VBA Access/Excel - Toggle Read/Write - excel

Using VBA Access
Is there a more efficient way of making an open read only excel file to read/write mode?
Or check if read only is true wait till read/write is active
I created a continuous loop that opens and closes the file till read/write is active. However sometimes it works sometimes it doesn't, frustrating.
I've looked into toggling read/write also Changefileaccess even SETATTR functions
Dim xl As Object
Set xl = CreateObject("Excel.Application")
Do Until xl.ActiveWorkbook.ReadOnly = False
xl.Quit
xl.Workbooks.Open ("C:\TEST\Test.xlsb")
If xl.ActiveWorkbook.ReadOnly = False Then Exit Do
Loop

Few observations:
There is a possibility that your code can go into an endless loop. Give some wait time before you recheck again.
Define the number of times the code should attempt re-opening.
Don't use CreateObject. CreateObject creates a new applicaiton. Use GetObject if you want to work with the already open file.
Check if the attribute is read only before you re-open the file.
See this example (Untested)
Sub Sample()
Dim objxlAp As Object, objxlWb As Object
Dim FlName As String
Dim NumberOfAttempt As Long
FlName = "C:\TEST\Test.xlsb"
Set objxlAp = GetObject(, "Excel.Application")
Set objxlWb = objxlAp.ActiveWorkbook
Do Until objxlWb.ReadOnly = False
objxlWb.Close (False)
If GetAttr(FlName) = vbReadOnly Then _
SetAttr FlName, vbNormal
objxlAp.Workbooks.Open (FlName)
If objxlWb.ReadOnly = False Then Exit Do
Wait 60 '<~~ Wait for 60 seconds. Change as Applicable
NumberOfAttempt = NumberOfAttempt + 1
If NumberOfAttempt > 5 Then
MsgBox "Tried reopening the file 5 times. Unable to do it. Exiting the loop"
Exit Do
End If
Loop
End Sub
Private Sub Wait(ByVal nSec As Long)
nSec = nSec + Timer
While nSec > Timer
DoEvents
Wend
End Sub
Important Note: A workbook can me made Read-Only using these two means
Right click on the file and set attribute as ReadOnly
File Save As - Read Only Recommended. The above code is not for this method.
My Assumptions:
You are not on a network where the file has been opened by a different user.

Can you check using GetAttr:
If ((GetAttr("C:\tmp\Test.xlsb")) And vbReadOnly) Then
Debug.Print "Do something, file is read only"
End If
Maybe change this property before open file?
Sub OpenRW()
Dim wb As Workbook
Set wb = Workbooks.Open("C:\tmp\Test.xlsb")
Debug.Print "1. ReadOnly? " & ActiveWorkbook.ReadOnly 'read only
wb.Close
SetAttr "C:\tmp\Test.xlsb", vbNormal
Set wb = Workbooks.Open("C:\tmp\Test.xlsb") 'read/write
Debug.Print "2. ReadOnly? " & ActiveWorkbook.ReadOnly
wb.Close
SetAttr "C:\tmp\Test.xlsb", vbReadOnly
Set wb = Workbooks.Open("C:\tmp\Test.xlsb")
Debug.Print "3. ReadOnly? " & ActiveWorkbook.ReadOnly 'read only
wb.Close
End Sub
Results:
OpenRW
1. ReadOnly? True
2. ReadOnly? False
3. ReadOnly? True

Related

From MS Access VBA close encrypted excel after failed password

I'm keeping an ID for API in an encrypted excel file (open to alternative suggestions) and using Microsoft access VBA to open the encrypted excel and extract the ID.
The issue is that it will not close excel if the password is incorrect. This code works fine if you enter the password correctly
Public Function getDeploymentID() As String
Dim fileLocation As String
fileLocation = "___DeploymentID.xlsx"
Dim objExcel As Object
Set objExcel = CreateObject("Excel.Application")
Dim wb As Excel.Workbook
On Error GoTo getDeploymentID_ERROR
MsgBox "The development password is in a password protected excel. It will prompt you for the password next"
Set wb = Workbooks.Open(fileLocation, True)
'User must enter password to continue. If they don't it'll error out on above line
DoEvents
'Get deploymentID
getDeploymentID = wb.Worksheets("Sheet1").Cells(1, 1)
'Close it
'wb.Close 'will close workbook, won't close excel
wb.Application.Quit 'will close workbook and excel
DoEvents
GoTo getDeploymentID_Cleanup
getDeploymentID_ERROR:
Debug.Print "Failed to open DeploymentID excel file. Error " & err.Number & ":" & err.description
objExcel.Quit 'THIS IS NOT WORKING
DoEvents
getDeploymentID_Cleanup:
Set wb = Nothing
Set objExcel = Nothing
End Function
I believe you need to access the Workbooks collection through the objExcel.
Set wb = objExcel.Workbooks.Open(fileLocation, True)
Then,
wb.Close 'close workbook
objExcel.Quit 'quit excel app
References:
Workbook.Close
Application.Quit
Regarding the structure of the function, I would add the error handling at the bottom and call Resume to avoid the 2nd GoTo statement.
'...
On Error GoTo getDeploymentID_ERROR
'...
'Get deploymentID
getDeploymentID = wb.Worksheets("Sheet1").Cells(1, 1)
getDeploymentID_Cleanup:
wb.Close
objExcel.Quit
Exit Function
getDeploymentID_ERROR:
Debug.Print "Failed to open DeploymentID excel file. Error " & err.Number & ":" & err.description
Resume getDeploymentID_Cleanup
End Function

Ensure Excel file is not being used before opening using VBA

I am using an interface made with Excel to allow users to concurrently edit a shared data file. To prevent multiple users from editing at the same time I made the following function to do the following:
Open the file
If the file was opened as a read-only, close the file and re-open until the file is opened as read-write or the maximum number of allowed attempts is crossed.
Function OpenTillCanEditC(refpath As String, pw As String) As Workbook
Dim wbtoopen As Workbook
Dim maxOpen As Long
Dim i As Long
Dim buttonClicked As Long
maxOpen = 10
i = 0
On Error GoTo ErrHandler:
Application.DisplayAlerts = False
Set wbtoopen = Workbooks.Open(refpath, True, Password:=pw, ReadOnly:=False)
Application.DisplayAlerts = True
While wbtoopen.ReadOnly And i < maxOpen
If wbtoopen.ReadOnly Then
wbtoopen.Close (False)
Application.Wait (Now + TimeValue("00:00:01"))
Set wbtoopen = Nothing
i = i + 1
If i >= maxOpen Then
buttonClicked = MsgBox("It appears the masterlist is currently being used by someone else. Do you want to retry opening?", vbRetryCancel)
If buttonClicked = vbRetry Then
maxOpen = maxOpen + 10
Application.DisplayAlerts = False
Set wbtoopen = Workbooks.Open(refpath, True, Password:=pw, ReadOnly:=False)
Application.DisplayAlerts = True
End If
Else
On Error GoTo ErrHandler:
Application.DisplayAlerts = False
Set wbtoopen = Workbooks.Open(refpath, True, Password:=pw, ReadOnly:=False)
Application.DisplayAlerts = True
End If
End If
Wend
Set OpenTillCanEditC = wbtoopen
Exit Function
ErrHandler:
Application.DisplayAlerts = True
If Err.Number = 1004 Then
MsgBox "The password keyed in is wrong."
Else
MsgBox "The masterlist found in " & refpath & " cannot be opened. It may be used by someone else or corrupted. If corrupted please open the file manually using Excel."
End If
Set wbtoopen = Nothing
Set OpenTillCanEditC = wbtoopen
End Function
OpenTillCanEditC will be used in a sub for updating as shown below:
Sub UpdateFile()
'Try to open
Dim datawb As Workbook
Dim filepath As String
Dim pw As String
filepath = "C:\Folder Containing File\Data File.xlsx"
pw = "password"
Set datawb = OpenTillCanEditC(filepath, pw)
If datawb Is Nothing Then
MsgBox "File cannot be opened or is currently in use."
Exit Sub
End If
'Do functions needed in the workbook here
datawb.Save()
datawb.Close
End Sub
However I keep getting either of the following two errors:
When multiple people are attempting to access the file, the OpenTillCanEditC function will still ask for a password even though it is already being keyed in.
datawb.Save() will sometimes throw an error stating that the save failed due to multiple users accessing the file.
How do I fix both of these issues to allow multiple users to edit a shared password-protected file using VBA?

Memory runs out by running continuous loop of saveas 2003 xls, reopen xlsm, close 2003 xls

[EDIT] I solved this: Integer doesn't have enough memory for the number of seconds overnight. So, changed to Long type and it works fine. Now I have a continuously updating Excel, database, CAD program and linked Excel fully automated 24-7! #SoHappy #CodeUpdatedBelow
From ThisWorkbook module in Excel, I am using Application.OnTime to call a sub in Module1, which will save a macro-enabled workbook to 2003 xls filetype, open an Access database, refresh a database table which is linked to that 2003 xls, close Access, open the original xlsm again (triggers a new timer) and finally close the 2003 xls. The timer is set on Workbook_Open and killed on Workbook_BeforeClose
For some reason it's leaking memory (I think), so the computer running the code runs out of memory by the afternoon (give or take).
Can anyone spot what I'm doing wrong, i.e. why it's hogging all that memory?
1 thing I'm aware of is I never actually close the xlsm file: it's Saved As a xls. This means the Workbook_BeforeClose event in theory never triggers to cancel the timer. But, since the time (public variable MyTime) is passed by then and it's not a recurring loop... I'm hoping that is not the cause.
I replaced the paths in Module1 with APATH for Access Path and EPATH for Excel Path - those are not erroneous variables, but hard-coded in the original (lazy, me?!)...
ThisWorkbook looks like this:
Dim MyTime As Date
Private Sub Workbook_Open()
'Just in case you need to debug
'Uncomment these 3 lines and click "No" on workbook open
'Dim Ans As Variant
'Ans = MsgBox("Do you want to run RefreshOnTime?", vbYesNo, "Yes/No")
'If Ans = vbYes Then RefreshOnTime
RefreshOnTime
End Sub
Sub RefreshOnTime()
Dim Seconds As Long
Dim OfficeOpens As Integer
Dim OfficeCloses As Integer
Dim Delay As Integer
'Delay in seconds
Delay = 240
OfficeOpens = 7
OfficeCloses = 17
'If in working hours
If Hour(Time) >= OfficeOpens And Hour(Time) < OfficeCloses Then
Seconds = Delay
'If in the morning
ElseIf Hour(Time) < OfficeOpens Then
Seconds = (OfficeOpens - Hour(Time)) * 3600 + Delay
'If after 5pm take 23:00 as highest hour of day, minus current hour
'Add 7 for morning
'Add 1 to take from 2300 to to midnight
ElseIf Hour(Time) >= OfficeCloses Then
Seconds = (23 - Hour(Time) + OfficeOpens + 1) * 3600 + Delay
End If
Debug.Print "Seconds = " & Seconds
MyTime = DateAdd("s", Seconds, Time)
Debug.Print "RefreshData will run at " & MyTime
'REPLACE MODULE1 with the right module
'REPLACE RefreshData with the name of your sub
Application.OnTime MyTime, "Module1.RefreshData"
End Sub
Private Sub Workbook_BeforeClose(Cancel As Boolean)
'REPLACE MODULE1 with the right module
'REPLACE RefreshData with the name of your sub
Application.OnTime MyTime, "Thisworkbook.RefreshData", , False
End Sub
Module1 looks like this:
Sub RefreshData()
'Application.ScreenUpdating = False
'Rebuild all calculations
Application.CalculateFullRebuild
'Refresh all data connections
Application.Workbooks("Materials.xlsm").RefreshAll
'Complete all refresh events before moving on
DoEvents
Debug.Print "Data Refreshed at " & Time
Call SaveAsOld
If Application.ScreenUpdating = False Then Application.ScreenUpdating = True
Debug.Print "Operation Complete at " & Time
End Sub
Sub SaveAsOld()
On Error Resume Next
'Disable Screen Updating
'Application.ScreenUpdating = False
'Save Current
ThisWorkbook.Save
DoEvents
Debug.Print "Macro Workbook Saved at " & Time
'Disable alerts
Application.DisplayAlerts = False
'Save As 2003 and overwrite
ThisWorkbook.SaveAs Filename:="EPATH\Materials_2003.xls", FileFormat:=56
Debug.Print "2003 xls copy saved at " & Time
'Enable Alerts
Application.DisplayAlerts = True
'Open the macro copy
Application.Workbooks.Open Filename:="EPATH\Materials.xlsm"
''Enable ScreenUpdating
'If Application.ScreenUpdating = False Then Application.ScreenUpdating = True
ThisWorkbook.Activate
Debug.Print "Macro version opened at " & Time
Call DBOpenClose
'Close the 2003 copy
Application.Workbooks("Materials_2003.xls").Close (SaveChanges = True)
Debug.Print "2003 xls copy closed at " & Time
End Sub
Sub DBOpenClose()
Debug.Print "DBOpenClose Started at " & Time
Dim appAccess As Access.Application
Set appAccess = New Access.Application
appAccess.Visible = True
Call OpenCurrentDatabase("APath\MCMat.mdb")
Debug.Print "Access db opened at " & Time
CurrentDb.TableDefs("CADT").RefreshLink
Debug.Print "CADT Table refreshed at " & Time
Call CloseCurrentDatabase
Debug.Print "Access DB Closed at " & Time
End Sub
Thanks so much for your help!
Seconds required more memory for the number of seconds overnight, that's why it always failed on the last run during open hours. Changed to Long type instead of integer.

SAP GUI export automatically opens excel

I am trying to write a macro that retrieves data from an ALV grid in SAP GUI. Everything is working fine up until the data exports. When exporting data from ALV grid to an .xlsx file, the file will automatically open after it exports.
I need my script to wait for the export to open, and then copy the data from the newly opened export file to the .xlsm file that the script is coming from.
If I try to activate the export.XLSX file immediately following the command to export the file in SAP GUI, I get a "subscript out of range" error. I thought maybe I could loop the activate command until it stops erroring (while the export.xlsx file is opening) but that causes excel to crash. What should I do?
Function funcLSAT(strEnv)
Dim wkbExport As Workbook
Dim strError As String
If Not IsObject(SapGuiApp) Then
Set SapGuiApp = CreateObject("Sapgui.ScriptingCtrl.1")
End If
If Not IsObject(Connection) Then
Set Connection = SapGuiApp.OpenConnection(strEnv, True)
End If
Set session = Connection.Children(0)
session.findById("wnd[0]/tbar[0]/okcd").Text = "[TCODE]"
session.findById("wnd[0]").sendVKey 0
session.findById("wnd[0]/tbar[1]/btn[8]").press
session.findById("wnd[0]/tbar[1]/btn[8]").press
session.findById("wnd[0]/usr/cntlG_CC_MCOUNTY/shellcont/shell").pressToolbarContextButton "&MB_EXPORT"
session.findById("wnd[0]/usr/cntlG_CC_MCOUNTY/shellcont/shell").selectContextMenuItem "&XXL"
session.findById("wnd[1]/usr/ctxtDY_PATH").Text = "[filepath]"
session.findById("wnd[1]/usr/ctxtDY_FILENAME").Text = "export.xlsx"
session.findById("wnd[1]/tbar[0]/btn[11]").press
Set session = Nothing
Set Connection = Nothing
Set SapGuiApp = Nothing
Do
On Error Resume Next
Windows("export.XLSX").Activate
Loop Until (Err.Number = 0)
On Error GoTo 0
Range("A2:AS2").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.Copy
End Function
I had the same issue time ago when my macro had to wait for a .txt to be created and then continue so I found this:
Dim Directory As String, File As String
Directory = ActiveWorkbook.Path & "\" 'path for the file
File = Directory & "datos.txt" 'name of the file along with the path
FindIt = Dir(File)
While Len(FindIt) = 0
FindIt = Dir(File)
Wend
Hope it helps.
Here is what I do to export as XSLX, copy over the sheet, and close.
Application.wait does not do what you would want it to do here. Application.wait does not release Excel and so the SAP file never opens. Setting a timer will release Excel so the file will load.
It may not be the most well written, but it works. First, do a function to be able to see if the files are open or not. Then, set a timer of 0.5 seconds or so to put on a loop. You could do a longer timer and probably wouldn't have to loop, but done with a short time keeps Excel from being released any longer than it has to. Once the file locked on to, it will copy over the contents and close the file, and will exit the loop. Then I also kill the file because, on next run, it needs to be gone if the first run the isfile open is going to pick up the old file. This is written to where it's supposed to grab the workbook if it's opened in another instance—though, I haven't tested that. The way my VBA workbook loads, SAP files end up loading in the same instance. Good Luck!
Function IsFileOpen(FileName As String)
Dim iFilenum As Long
Dim iErr As Long
On Error Resume Next
iFilenum = FreeFile()
Open FileName For Input Lock Read As #iFilenum
Close iFilenum
iErr = Err
On Error GoTo 0
Select Case iErr
Case 0: IsFileOpen = False
Case 70: IsFileOpen = True
Case Else: Error iErr
End Select
End Function
Dim xlfile as string, xldir as string
Dim PauseTime, Start
Dim control As Long
Dim xlapp As Object
Dim wb as workbook
xldir= "Your file folder path here"
control = 0
xlfile = "Your filename here"
Do Until control = 5
PauseTime = 0.5 ' Set duration.
Start = Timer ' Set start time.
Do While Timer < Start + PauseTime
DoEvents ' Yield to other processes.
Loop
If control = 1 And IsFileOpen(xldir & xlfile) = False Then
Exit Do
End If
If IsFileOpen(xldir & xlfile) = True Then
Set xlapp = GetObject(xldir & xlfile).Application
If control = 0 Then
Workbooks(xlfile).Sheets(1).Name = Left(xlfile, Len(xlfile) - 5)
Workbooks(xlfile).Sheets(Left(xlfile, Len(xlfile) - 5)).Copy
before:=ThisWorkbook.Sheets(1)
ThisWorkbook.Activate
control = 1
End If
For Each wb In Application.Workbooks
If wb.Name = xlfile Then
wb.Close
End If
Next wb
End If
Loop
control = 0
kill(xldir&xlfile)

Receiving ActiveX Automation: Bad Index Error Message

Prelude
I am starting a new project, and basically I am using Excel as a log for another program I am using. With this being said, this is a mixture of VBA (Only when using Excel's object) and VB6 (the main "host" programming language). This is why both languages are tagged as I anticipate hateful comments from the use of tags; I am looking for a solution in either/mixture of both programming languages!!
Also, I am aware some VBA activists will say to never use ActiveSheet. I am not concerned about this and I would like to say thank you ahead of time. I have one sheet in this workbook as it's primary function is to serve as a log. The ActiveSheet will always be the one and only sheet.
I have the following code, and I am not too familiar with Setting a workbook as an object, which is likely the reason I receive the Bad Index error.
Sub Test()
' Checking if Excel is open, if not, open it.
Dim xL As Object, wBook As Object, iCloseThings As Byte
On Error Resume Next
Set xL = GetObject(, "Excel.Application")
On Error GoTo 0
If xL Is Nothing Then
iCloseThings = 1 ' Set Excel to close only if it was never open
Set xL = CreateObject("Excel.Application")
End If
Set wBook = xL.Workbooks("C:\Users\<UserName>\Documents\<WorkBook>.xlsx").ActiveSheet
If iCloseThings = 1 Then xL.Quit
End sub
What I need assistance with is how would I properly set this object to point to the exact workbook I have in the above example? All I have ever known to do was something such as Set wBook = XL.Workbooks("<WorkBook>.xlsx").ActiveSheet because I knew such workbook would already be open. But with the possibility of it not being open, I need something a little more flexible.
Thanks for your assistance!
you need some different cases handling, mainly depending if the wanted workbook is already open or not should a running Excel session be "caught"
you may want to use some dedicated Functions not to clutter your main code and be more effective in both debugging and maintaining your code, like follows
Option Explicit
Sub Test()
' Checking if Excel is open, if not, open it.
Dim xL As Object, wBook As Object, wSheet As Object, iCloseThings As Byte
Set xL = GetExcel(iCloseThings)
Set wBook = GetExcelWorkbook(xL, "C:\Users\<UserName>\Documents\<WorkBook>.xlsx")
If wBook Is Nothing Then Exit Sub
Set wSheet = wBook.ActiveSheet
If iCloseThings = 1 Then xL.Quit
End Sub
Function GetExcel(iCloseThings As Byte) As Object
On Error Resume Next
Set GetExcel = GetObject(, "Excel.Application")
On Error GoTo 0
If GetExcel Is Nothing Then
iCloseThings = 1 ' Set Excel to close only if it was never open
Set GetExcel = CreateObject("Excel.Application")
End If
End Function
Function GetExcelWorkbook(xL As Object, wbFullName As String) As Object
Dim wbName As String
wbName = Right(wbFullName, Len(wbFullName) - InStrRev(wbFullName, "\"))
On Error Resume Next
Set GetExcelWorkbook = xL.Workbooks(wbName)
On Error GoTo 0
If GetExcelWorkbook Is Nothing Then
Set GetExcelWorkbook = xL.Workbooks.Open(wbFullName)
Else
If GetExcelWorkbook.Path & "\" & wbName <> wbFullName Then
MsgBox "A workbook with the wanted name '" & wbName & "' is already open but its path doesn't match the required one" _
& vbCrLf & vbCrLf & "Close the already open workbook and run this macro again", vbCritical + vbInformation
Set GetExcelWorkbook = Nothing
Else
MsgBox "Wanted workbook is already open", vbInformation
End If
End If
End Function

Resources