Я использую VBA в своей базе данных Access для запуска пакетного файла, связанного со сценарием PS1.
Это все работает, как задумано. Проблема в том, что я хочу запустить некоторые запросы после завершения этого действия, но в нынешнем виде мне нужно присматривать за всем этим. Поэтому я ищу решение, позволяющее приостановить работу VBA во время выполнения пакета.
Я нашел эту статью: https://danwagner.co/how-to-run-a-batch-file-and-wait-until-it-finishes-with-vba/
Но решение не работает для меня по какой-то причине. Пакет выполняется, но VBA просто движется вперед без пауз.
Вот мой код:
Private Sub Button_UpdateOffline_Click()
Dim strCommand As String
Dim lngErrorCode As Long
Dim wsh As WshShell
Set wsh = New WshShell
DoCmd.OpenForm "Please_Wait"
'Run the batch file using the WshShell object
strCommand = Chr(34) & _
"C:\Users\Rip\Q_Update.bat" & _
Chr(34)
lngErrorCode = wsh.Run(strCommand, _
WindowStyle:=0, _
WaitOnReturn:=True)
If lngErrorCode <> 0 Then
MsgBox "Uh oh! Something went wrong with the batch file!"
Exit Sub
End If
DoCmd.Close acForm, "Please_Wait"
End Sub
Вот мой пакетный код, если это поможет:
START PowerShell.exe -ExecutionPolicy Bypass -Command "& 'C:\Users\Rip\PS1\OfflineFAQ_Update.ps1' "
Простой метод: создайте файл в VBA, запустите пакет, VBA подождет, пока файл существует, пакет удалит файл в качестве последнего шага, VBA продолжит...
Вы должны использовать -File
для запуска скрипта PowerShell, а не -Command
. Пример @Start "PS1" %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy RemoteSigned -File "C:\Users\Rip\PS1\OfflineFAQ_Update.ps1"
.
Магу! Это гениально и легко! На самом деле я не делал того, что вы здесь предложили, но это привело меня к решению. Файл PS1 обновлен как файл excel, поэтому мой код ищет файл блокировки excel! Как только файл блокировки исчезнет, код продолжит работу!
@Compo, спасибо за обновление кода! Я не уверен, в чем разница, но он работает гладко, так что это важно для меня! Вы случайно не знаете, могу ли я добавить что-нибудь в код, чтобы окно не появлялось? В основном запускаете код в фоновом режиме?
Для этого @Lowendz113 посмотрите здесь.
Ваш пакетный код запускает PowerShell, а затем закрывается.
VBA ждет, пока ваш пакетный код запустит PowerShell, а затем продолжает работу. У него нет способа узнать, что вы на самом деле хотите дождаться завершения PowerShell, поскольку, если вы хотите дождаться этого, вам также придется заставить свой пакетный скрипт ждать.
Итак, либо измените батч-код, включив в него /WAIT
, в дополнение к изменениям, предложенным в комментариях:
START /wait PowerShell.exe -ExecutionPolicy Bypass -Command "& 'C:\Users\Rip\PS1\OfflineFAQ_Update.ps1' "
Или откройте PowerShell напрямую, без промежуточного пакетного файла:
strCommand = "PowerShell.exe -ExecutionPolicy Bypass -Command ""& 'C:\Users\Rip\PS1\OfflineFAQ_Update.ps1' """
Ну святое дерьмо. Это было легко! Здоровья, приятель!
Я выбрал метод /wait, так как его было проще реализовать.
Используйте вызов API WaitForSingleObject. Это требует немного длинного кода, но его очень просто реализовать, когда он заключен в функцию, подобную ShellWait
ниже.
Таким образом, вы можете вызвать команду PowerShell напрямую, как в этом примере:
' Unblock a file or all files of a folder.
'
' 2022-10-18. Gustav Brock, Cactus Data ApS, CPH.
'
Public Function UnblockFiles( _
ByVal Path As String) _
As Boolean
Const CommandMask As String = "PowerShell -command {0}"
Const ArgumentMask As String = "Dir ""{0}"" -Recurse | Unblock-File"
Const WindowStyle As Long = VbAppWinStyle.vbHide
Dim Argument As String
Dim Command As String
Dim Result As Long
Dim Success As Boolean
If Dir(Path, vbDirectory) = "" Then
' Path is neither a file nor a folder.
Else
' Continue.
Argument = Replace(ArgumentMask, "{0}", Path)
Command = Replace(CommandMask, "{0}", Argument)
Result = ShellWait(Command, WindowStyle)
Success = Not CBool(Result)
End If
UnblockFiles = Success
End Function
Вы увидите, что command
состоит из двух верхних констант и переменной. Вы должны иметь возможность изменять их для своей команды.
ShellWait
Я использую:
' General constants.
'
' Wait forever.
Private Const Infinite As Long = &HFFFF
' Process Security and Access Rights.
'
' The right to use the object for synchronization.
' This enables a thread to wait until the object is in the signaled state.
Private Const Synchronize As Long = &H100000
' Constants for WaitForSingleObject.
'
' The specified object is a mutex object that was not released by the thread
' that owned the mutex object before the owning thread terminated.
' Ownership of the mutex object is granted to the calling thread and the
' mutex state is set to nonsignaled.
Private Const StatusAbandonedWait0 As Long = &H80
Private Const WaitAbandoned As Long = StatusAbandonedWait0 + 0
' The state of the specified object is signaled.
Private Const StatusWait0 As Long = &H0
Private Const WaitObject0 As Long = StatusWait0 + 0
' The time-out interval elapsed, and the object's state is nonsignaled.
Private Const WaitTimeout As Long = &H102
' The function has failed. To get extended error information, call GetLastError.
Private Const WaitFailed As Long = &HFFFFFFFF
' Missing enum when using late binding.
'
#If EarlyBinding = False Then
Public Enum IOMode
ForAppending = 8
ForReading = 1
ForWriting = 2
End Enum
#End If
' API declarations.
' Opens an existing local process object.
' If the function succeeds, the return value is an open handle
' to the specified process.
' If the function fails, the return value is NULL (0).
' To get extended error information, call GetLastError.
'
#If VBA7 Then
Private Declare PtrSafe Function OpenProcess Lib "kernel32" ( _
ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwProcessId As Long) _
As LongPtr
#Else
Private Declare Function OpenProcess Lib "kernel32" ( _
ByVal dwDesiredAccess As Long, _
ByVal bInheritHandle As Long, _
ByVal dwProcessId As Long) _
As Long
#End If
' The WaitForSingleObject function returns when one of the following occurs:
' - the specified object is in the signaled state.
' - the time-out interval elapses.
'
' The dwMilliseconds parameter specifies the time-out interval, in milliseconds.
' The function returns if the interval elapses, even if the object's state is
' nonsignaled.
' If dwMilliseconds is zero, the function tests the object's state and returns
' immediately.
' If dwMilliseconds is Infinite, the function's time-out interval never elapses.
'
#If VBA7 Then
Private Declare PtrSafe Function WaitForSingleObject Lib "kernel32" ( _
ByVal hHandle As LongPtr, _
ByVal dwMilliseconds As Long) _
As Long
#Else
Private Declare Function WaitForSingleObject Lib "kernel32" ( _
ByVal hHandle As Long, _
ByVal dwMilliseconds As Long) _
As Long
#End If
' Closes an open object handle.
' If the function succeeds, the return value is nonzero.
' If the function fails, the return value is zero.
' To get extended error information, call GetLastError.
'
#If VBA7 Then
Private Declare PtrSafe Function CloseHandle Lib "kernel32" ( _
ByVal hObject As LongPtr) _
As Long
#Else
Private Declare Function CloseHandle Lib "kernel32" ( _
ByVal hObject As Long) _
As Long
#End If
' Shells out to an external process and waits until the process ends.
' Returns 0 (zero) for no errors, or an error code.
'
' The call will wait for an infinite amount of time for the process to end.
' The process will seem frozen until the shelled process terminates. Thus,
' if the shelled process hangs, so will this.
'
' A better approach could be to wait a specific amount of time and, when the
' time-out interval expires, test the return value. If it is WaitTimeout, the
' process is still not signaled. Then either wait again or continue with the
' processing.
'
' Waiting for a DOS application is different, as the DOS window doesn't close
' when the application is done.
' To avoid this, prefix the application command called (shelled to) with:
' "command.com /c " or "cmd.exe /c ".
'
' For example:
' Command = "cmd.exe /c " & Command
' Result = ShellWait(Command)
'
' 2018-04-06. Gustav Brock. Cactus Data ApS, CPH.
'
Public Function ShellWait( _
ByVal Command As String, _
Optional ByVal WindowStyle As VbAppWinStyle = vbNormalNoFocus) _
As Long
Const InheritHandle As Long = &H0
Const NoProcess As Long = 0
Const NoHandle As Long = 0
#If VBA7 Then
Dim ProcessHandle As LongPtr
#Else
Dim ProcessHandle As Long
#End If
Dim DesiredAccess As Long
Dim ProcessId As Long
Dim WaitTime As Long
Dim Closed As Boolean
Dim Result As Long
If Len(Trim(Command)) = 0 Then
' Nothing to do. Exit.
Else
ProcessId = Shell(Command, WindowStyle)
If ProcessId = NoProcess Then
' Process could not be started.
Else
' Get a handle to the shelled process.
DesiredAccess = Synchronize
ProcessHandle = OpenProcess(DesiredAccess, InheritHandle, ProcessId)
' Wait "forever".
WaitTime = Infinite
' If successful, wait for the application to end and close the handle.
If ProcessHandle = NoHandle Then
' Should not happen.
Else
' Process is running.
Result = WaitForSingleObject(ProcessHandle, WaitTime)
' Process ended.
Select Case Result
Case WaitObject0
' Success.
Case WaitAbandoned, WaitTimeout, WaitFailed
' Know error.
Case Else
' Other error.
End Select
' Close process.
Closed = CBool(CloseHandle(ProcessHandle))
If Result = WaitObject0 Then
' Return error if not closed.
Result = Not Closed
End If
End If
End If
End If
ShellWait = Result
End Function
Обзор devhut.net/vba-wmi-determine-if-a-process-is-running-or-not