#c# #vba #ms-access #winapi
#c# #vba #ms-access #winapi
Вопрос:
У меня есть код, который показан ниже, главное — внести некоторые изменения в файл .accdb, сохранив существующие атрибуты CreationTime, LastAccessTime и LastWriteTime файла.
Все работает отлично, но я столкнулся с этой проблемой: исходные проекты MS access (тысячи файлов .accdb) содержат макросы VBA. Стандартная политика: «Отключить все макросы с уведомлением». Таким образом, как только пользователь нажал на кнопку, файл сохраняет его предпочтения (только для этого файла) — и это нормально.
Но после того, как я внесу изменения в файл с помощью следующего скрипта, он сбрасывает настройки безопасности именно для этого файла, и при следующем открытии базы данных появляется желтое «Предупреждение о безопасности» (все макросы и активное содержимое одинаковы, ничего нового!).).
Проблема в том, что у меня тысячи файлов accdb. Мне нужно внести небольшие изменения, но сохранить существующие атрибуты. И когда пользователи откроют эти файлы, не должно быть никакой новой панели предупреждений безопасности.
Я могу, но я не хочу:
-
измените настройки безопасности для активного содержимого
-
сделайте эту папку доверенной папкой
-
любые другие изменения параметров безопасности, такие как Regedit и так далее.
Я НЕ добавляю никаких макросов или активного содержимого, появляется сообщение, потому что, я думаю, некоторые настройки после использования CreateFileA были изменены. (например, если я вручную переименую файл, MS Access подумает, что это другой файл, и отобразит панель безопасности, и это правильно; но почему она появляется после изменения атрибутов файла?)
Спасибо! PS Мы используем MS Access 2016.
Option Explicit
Public Const GENERIC_WRITE = amp;H40000000, GENERIC_READ = amp;H80000000, FILE_ATTRIBUTE_NORMAL = amp;H80, OPEN_EXISTING = 3
Public Type FileTime: dwLowDateTime As Long: dwHighDateTime As Long: End Type
Public Type SYSTEMTIME: wYear As Integer: wMonth As Integer: wDayOfWeek As Integer: wDay As Integer: wHour As Integer: wMinute As Integer: wSecond As Integer: wMilliseconds As Integer: End Type
Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFilename As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Declare Function SetFileTime Lib "kernel32" (ByVal hFile As Long, lpCreationTime As FileTime, lpLastAccessTime As FileTime, lpLastWriteTime As FileTime) As Long
Declare Function GetFileTime Lib "kernel32" (ByVal hFile As Long, lpCreationTime As FileTime, lpLastAccessTime As FileTime, lpLastWriteTime As FileTime) As Long
Declare Function SystemTimeToFileTime Lib "kernel32" (lpSystemTime As SYSTEMTIME, lpFileTime As FileTime) As Long
Declare Function LocalFileTimeToFileTime Lib "kernel32" (lpFileTime As FileTime, lpLocalFileTime As FileTime) As Long
Declare Function FileTimeToSystemTime Lib "kernel32" (lpFileTime As FileTime, lpSystemTime As SYSTEMTIME) As Long
Declare Function FileTimeToLocalFileTime Lib "kernel32" (lpLocalFileTime As FileTime, lpFileTime As FileTime) As Long
Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Public GlobCreationDate As Date, GlobLastAccess As Date, GlobLastWrite As Date
Public FileAddress As String
Function DTtoFT(ByVal DT As Date) As FileTime 'DateTimeToFileTime
Dim ST As SYSTEMTIME, lTime As FileTime
ST.wYear = Year(DT): ST.wMonth = Month(DT): ST.wDay = Day(DT): ST.wHour = Hour(DT): ST.wMinute = Minute(DT): ST.wSecond = Second(DT)
SystemTimeToFileTime ST, lTime
LocalFileTimeToFileTime lTime, DTtoFT
End Function
Function FTtoDT(FT As FileTime) As Date 'FileTimeToDateTime
Dim lTime As FileTime, ST As SYSTEMTIME
FileTimeToLocalFileTime FT, lTime
FileTimeToSystemTime lTime, ST
FTtoDT = DateSerial(ST.wYear, ST.wMonth, ST.wDay) TimeSerial(ST.wHour, ST.wMinute, ST.wSecond)
End Function
Sub GetFTime(fName As String, Creation As Date, LastAccess As Date, LastWrite As Date)
Dim hFile As Long, ct As FileTime, at As FileTime, wt As FileTime
hFile = CreateFile(fName, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
GetFileTime hFile, ct, at, wt
CloseHandle hFile
Creation = FTtoDT(ct): LastAccess = FTtoDT(at): LastWrite = FTtoDT(wt)
GlobCreationDate = FTtoDT(ct): GlobLastAccess = FTtoDT(at): GlobLastWrite = FTtoDT(wt)
End Sub
Sub SetFTime(fName As String, Optional Creation As Date = -657434, Optional LastAccess As Date = -657434, Optional LastWrite As Date = -657434)
Dim hFile As Long, ct As Date, at As Date, wt As Date
If Creation = -657434 Or LastAccess = -657434 Or LastWrite = -657434 Then
GetFTime fName, ct, at, wt
If Creation = -657434 Then Creation = ct
If LastAccess = -657434 Then LastAccess = at
If LastWrite = -657434 Then LastWrite = wt
End If
hFile = CreateFile(fName, GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
SetFileTime hFile, DTtoFT(Creation), DTtoFT(LastAccess), DTtoFT(LastWrite)
CloseHandle hFile
End Sub
Sub SetChanges()
Dim T1 As Date, T2 As Date, T3 As Date, T4 As Date, T5 As Date, T6 As Date
Dim cn As Object, strQuery As String
Dim strPathToDB As String
FileAddress = "D:ProjectsDB1.accdb"
GetFTime FileAddress, T1, T2, T3
'some code right here
SetFTime FileAddress, T1, T2, T3
'
End Sub
Комментарии:
1. Насколько я помню, Windows сохранит контрольную сумму в стороне от утверждения файла в реестре. Так что, возможно, одна или несколько дат, которые вы изменяете, относятся к этой контрольной сумме, что приводит к недействительности утверждения. Возможно, вам следует использовать надежные местоположения ( support.office.com/en-us/article /… ) вместо этого?
2. Access также записывает информацию в файл, поэтому, я думаю, вам придется делать то, что вы «можете, но [я] не хочу». Поскольку вы контролируете весь процесс, ваш первый метод «изменить настройки безопасности для активного содержимого» кажется мне безвредным, так как вы можете сбросить его, когда закончите.
3. @Необработанное исключение Спасибо, вы были правы! Кажется, он использует дату создания, местоположение файла и имя файла в качестве триггера — я точно не знаю, используются ли они в сочетании как контрольная сумма или что-то в этом роде, но если вы измените какой-либо из этих атрибутов, это приведет к недействительности утверждения.
Ответ №1:
Хорошо, ребята, я нашел решение, так что, возможно, это кому-то поможет.
Причина, по которой MS Access показывал предупреждения безопасности, заключается в том, что сценарий технически изменял дату создания, хотя это была та же дата.
Итак, теперь я могу заявить, что MS Access использует дату создания, местоположение файла и имя файла в качестве триггера, я точно не знаю, используются ли они в сочетании как контрольная сумма или что-то в этом роде, но если вы измените любой из этих атрибутов, Access подумает, что это может быть другой файл, я думаю.
Я просмотрел API MS Windows и нашел это:
Функция, использующая структуру FILETIME, может допускать значения, отличные от нуля или положительных значений, обычно задаваемых членами dwLowDateTime и dwHighDateTime. Например, функция SetFileTime использует 0xFFFFFFFF, чтобы указать, что предыдущее время доступа к файлу должно быть сохранено.
Поэтому я немного изменил код:
Option Explicit
Public Const GENERIC_WRITE = amp;H40000000, GENERIC_READ = amp;H80000000, FILE_ATTRIBUTE_NORMAL = amp;H80, OPEN_EXISTING = 3
Public Type FileTime: dwLowDateTime As Long: dwHighDateTime As Long: End Type
Public Type SYSTEMTIME: wYear As Integer: wMonth As Integer: wDayOfWeek As Integer: wDay As Integer: wHour As Integer: wMinute As Integer: wSecond As Integer: wMilliseconds As Integer: End Type
Declare Function CreateFile Lib "kernel32" Alias "CreateFileA" (ByVal lpFilename As String, ByVal dwDesiredAccess As Long, ByVal dwShareMode As Long, ByVal lpSecurityAttributes As Long, ByVal dwCreationDisposition As Long, ByVal dwFlagsAndAttributes As Long, ByVal hTemplateFile As Long) As Long
Declare Function SetFileTime Lib "kernel32" (ByVal hFile As Long, lpCreationTime As FileTime, lpLastAccessTime As FileTime, lpLastWriteTime As FileTime) As Long
Declare Function GetFileTime Lib "kernel32" (ByVal hFile As Long, lpCreationTime As FileTime, lpLastAccessTime As FileTime, lpLastWriteTime As FileTime) As Long
Declare Function SystemTimeToFileTime Lib "kernel32" (lpSystemTime As SYSTEMTIME, lpFileTime As FileTime) As Long
Declare Function LocalFileTimeToFileTime Lib "kernel32" (lpFileTime As FileTime, lpLocalFileTime As FileTime) As Long
Declare Function FileTimeToSystemTime Lib "kernel32" (lpFileTime As FileTime, lpSystemTime As SYSTEMTIME) As Long
Declare Function FileTimeToLocalFileTime Lib "kernel32" (lpLocalFileTime As FileTime, lpFileTime As FileTime) As Long
Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Public GlobCreationDate As Date, GlobLastAccess As Date, GlobLastWrite As Date
Public FileAddress As String
Function DTtoFT(ByVal DT As Date) As FileTime
Dim ST As SYSTEMTIME, lTime As FileTime
ST.wYear = Year(DT): ST.wMonth = Month(DT): ST.wDay = Day(DT): ST.wHour = Hour(DT): ST.wMinute = Minute(DT): ST.wSecond = Second(DT)
SystemTimeToFileTime ST, lTime
LocalFileTimeToFileTime lTime, DTtoFT
End Function
Function FTtoDT(FT As FileTime) As Date
Dim lTime As FileTime, ST As SYSTEMTIME
FileTimeToLocalFileTime FT, lTime
FileTimeToSystemTime lTime, ST
FTtoDT = DateSerial(ST.wYear, ST.wMonth, ST.wDay) TimeSerial(ST.wHour, ST.wMinute, ST.wSecond)
End Function
Sub GetFTime(fName As String, Creation As Date, LastAccess As Date, LastWrite As Date)
Dim hFile As Long, ct As FileTime, at As FileTime, wt As FileTime
hFile = CreateFile(fName, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
GetFileTime hFile, ct, at, wt
CloseHandle hFile
Creation = FTtoDT(ct): LastAccess = FTtoDT(at): LastWrite = FTtoDT(wt)
GlobCreationDate = FTtoDT(ct): GlobLastAccess = FTtoDT(at): GlobLastWrite = FTtoDT(wt)
End Sub
Sub SetFTime(fName As String, Optional Creation As Date = -657434, Optional LastAccess As Date = -657434, Optional LastWrite As Date = -657434)
Dim hFile As Long, ct As Date, at As Date, wt As Date
Dim CreateF As FileTime, AccessF As FileTime
If Creation = -657434 Or LastAccess = -657434 Or LastWrite = -657434 Then
GetFTime fName, ct, at, wt
If Creation = -657434 Then Creation = ct
If LastAccess = -657434 Then LastAccess = at
If LastWrite = -657434 Then LastWrite = wt
End If
CreateF.dwLowDateTime = 0: CreateF.dwHighDateTime = 0
AccessF.dwLowDateTime = 0: AccessF.dwLowDateTime = 0
hFile = CreateFile(fName, GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)
SetFileTime hFile, CreateF, AccessF, DTtoFT(LastWrite)
CloseHandle hFile
End Sub
Sub SetChanges()
Dim T1 As Date, T2 As Date, T3 As Date
Dim cn As Object, strQuery As String
Dim strPathToDB As String
FileAddress = "D:ProjectsDB1.accdb"
GetFTime FileAddress, T1, T2, T3
'some code right here'
SetFTime FileAddress, T1, T2, T3
End Sub
Как это работает: теперь скрипт не вносит никаких изменений в CreationTime и LastAccessTime и использует предыдущие атрибуты файла. Но он изменяет LastWriteTime сразу после выполнения некоторого кода. И, наконец, это работает без сброса безопасности для конечного пользователя.