Заставить VBA ждать завершения пакетного файла

Я использую 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 продолжит...

Magoo 15.11.2022 22:09

Вы должны использовать -File для запуска скрипта PowerShell, а не -Command. Пример @Start "PS1" %SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy RemoteSigned -File "C:\Users\Rip\PS1\OfflineFAQ_Update.ps1".

Compo 15.11.2022 22:14

Магу! Это гениально и легко! На самом деле я не делал того, что вы здесь предложили, но это привело меня к решению. Файл PS1 обновлен как файл excel, поэтому мой код ищет файл блокировки excel! Как только файл блокировки исчезнет, ​​код продолжит работу!

Lowendz113 16.11.2022 19:53

@Compo, спасибо за обновление кода! Я не уверен, в чем разница, но он работает гладко, так что это важно для меня! Вы случайно не знаете, могу ли я добавить что-нибудь в код, чтобы окно не появлялось? В основном запускаете код в фоновом режиме?

Lowendz113 16.11.2022 20:22

Для этого @Lowendz113 посмотрите здесь.

Compo 16.11.2022 21:03
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
81
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Ваш пакетный код запускает 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' """

Ну святое дерьмо. Это было легко! Здоровья, приятель!

Lowendz113 16.11.2022 19:59

Я выбрал метод /wait, так как его было проще реализовать.

Lowendz113 16.11.2022 20:01

Используйте вызов 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

Другие вопросы по теме