Datei in enaio auschecken und automatisch wieder einchecken

Des Öfteren kommt diese Frage auf, und eine definitive Antwort ist schwierig. Auch weil Windows und die typischen Anwendungen ein bewegliches Ziel sind:

Wie kann aus dem Rich-Client von enaio eine Datei im Format X geöffnet, bearbeitet und dafür automatisch ausgecheckt und nach der Bearbeitung automatisch wieder eingecheckt werden?

Vergleiche dazu z. B. die Diskussion Pdf Dateien automatisch einchecken aus dem letzten Sommer.

Unsere grundsätzliche Antwort ist es, dies von Tools wie Embedded Office, Embedded Documents etc. erledigen zu lassen, was auch im (enaio-) Web, auf Tablets etc. funktioniert und das Problem behebt. Wenn dies aber (noch) nicht in allen Unternehmen möglich ist, bleibt die Frage offen.

Für Office-Dokumente (XLSX, DOCX etc.) gibt es die Microsoft Office-Addins von OPTIMAL SYSTEMS. Solange Microsoft noch Lust hat, MS Office für den Desktop zu verkaufen, ist dies eine Option. Diese passen Word etc. so an, dass dies ermöglicht wird, weil dann sozusagen Word „selbst“ das Signal gibt, wenn ein Dokument wieder eingecheckt ist. Für andere Dokumentformate kann es sein, dass man im Regen steht.

Über eine externe Anwendung könnte dies automatisiert werden, z. B. so:

Jetzt kommt das eigentliche Problem: Früher haben die meisten Windows-Anwendungen einen WriteLock auf geöffnete Dokumente gesetzt, welcher zu erkennen war. Dies wäre ein Beispiel-checkout_edit_checkin-Script für solche Anwendungen:

Option Explicit
Dim FSO: Set FSO = CreateObject("Scripting.FileSystemObject")
Dim AX: Set AX = CreateObject("Optimal_AS.Application")

Main(): Function Main()
    Dim DocIdAndType: Set DocIdAndType = GetPassedDocIdAndType()
    Dim FileName
    Dim AxResult: AxResult = AX.CheckOutDocument(DocIdAndType("id"), DocIdAndType("type"), FSO.GeTSpecialFolder(2).Path, FileName)
    If AxResult <> 0 Then
        MsgBox AX.GetLastError, 48, "Fehler beim Chekcout"
        Exit Function
    End If

    Dim WShell: Set WShell = CreateObject("Wscript.Shell")
    WShell.Run """" & FileName & """", 1, False

    TimeOutWriteLock FileName
    Do While Not IsFileWritable(FileName)
        WScript.Sleep 500
    Loop

    AxResult = AX.CheckInDocument(DocIdAndType("id"), DocIdAndType("type"), FSO.GeTSpecialFolder(2).Path)
    If AxResult <> 0 Then
        AxResult = AX.UndoCheckOut(DocIdAndType("id"), DocIdAndType("type"))
        If AxResult <> 0 Then
            MsgBox AX.GetLastError, 48, "Fehler beim Checkin"
            Exit Function
        End If
    End If

    'Optional: Einen der denkbaren Worarounds für das Fehlen der osjxRefreshObjectInLists im AX-Objekt
    WShell.AppActivate "enaio"
    WScript.Sleep 200
    WShell.SendKeys "{F5}"
End Function

Sub TimeOutWriteLock(FileName)
    Dim i: For i = 1 To 30
        WScript.Sleep 250
        If Not IsFileWritable(FileName) Then
            Exit Sub
        End If
    Next
End Sub

Function IsFileWritable(FilePath)
    On Error Resume Next
    Dim TS: Set TS = FSO.OpenTextFile(FilePath, 8, False)
    If Err.Number = 0 Then
        TS.Close
        IsFileWritable = True
    Else
        IsFileWritable = False
    End If
    Err.Clear
    On Error GoTo 0
End Function

Function GetPassedDocIdAndType()
    Dim ArgObj: Set ArgObj = WScript.ArgumenTS
    Dim IniFilePath: IniFilePath = ArgObj(0)
    Dim osParamFile: Set osParamFile = FSO.OpenTextFile(IniFilePath)
    Dim IniFileContent: IniFileContent = OsParamFile.ReadAll
    OsParamFile.Close
    FSO.DeleteFile IniFilePath, True
    Dim DocIDs: DocIDs = Split(IniFileContent, vbCrLf)    
    Dim DocIdAndType: DocIdAndType = Split(DocIDs(0), ",")
    Dim Result: Set Result = CreateObject("Scripting.Dictionary")
    Result("id") = DocIdAndType(0)
    Result("type") = DocIdAndType(1)
    Set GetPassedDocIdAndType = Result
End Function

Allerdings machen dies immer weniger Anwendungen, sogar die MS-Tools notepad.exe, mswrite.exe und mspaint.exe, welche dies 30 Jahre getan haben, tun es heute nicht mehr (was ich grundsätzlich begrüsse). (Ganz abgesehen davon, dass Microsoft notepad derzeit massiv umbaut und Write/Paint abgekündigt und aus neuen Windows-Build verbannt hat…)

Deshalb müsste man für solche Anwendungen, wenn man sie mit Auto-Checkin nutzen will, einen anderen Erkennungsmechanismus nutzen, z. B. ob die Anwendung selbst noch läuft. Dies wäre dann ein Beispiel-checkout_edit_checkin-Script für solche nicht-sperrenden Anwendungen:

Option Explicit
Dim FSO: Set FSO = CreateObject("Scripting.FileSystemObject")
Dim AX: Set AX = CreateObject("Optimal_AS.Application")
Dim WMI: Set WMI = GetObject("winmgmts:\\.\root\cimv2")

Main(): Function Main()
    Dim DocIdAndType: Set DocIdAndType = GetPassedDocIdAndType()
    Dim FileName
    Dim AxResult: AxResult = AX.CheckOutDocument(DocIdAndType("id"), DocIdAndType("type"), FSO.GetSpecialFolder(2).Path, FileName)
    If AxResult <> 0 Then
        MsgBox AX.GetLastError, 48, "Fehler beim Checkout"
        Exit Function
    End If

    Dim WShell: Set WShell = CreateObject("Wscript.Shell")

    Dim PidsBefore: Set PidsBefore = GetRunningPIDs()
    WShell.Run """" & FileName & """", 1, False

    Dim EditorPID: EditorPID = WaitForNewPID(PidsBefore, 10)
    If EditorPID = -1 Then
        MsgBox "Could not detect editor process.", 48, "Fehler"
        Exit Function
    End If

    WaitForPIDExit EditorPID

    AxResult = AX.CheckInDocument(DocIdAndType("id"), DocIdAndType("type"), FSO.GetSpecialFolder(2).Path)
    If AxResult <> 0 Then
        AxResult = AX.UndoCheckOut(DocIdAndType("id"), DocIdAndType("type"))
        If AxResult <> 0 Then
            MsgBox AX.GetLastError, 48, "Fehler beim Checkin"
            Exit Function
        End If
    End If

    WShell.AppActivate "enaio"
    WScript.Sleep 200
    WShell.SendKeys "{F5}"
End Function

Function GetRunningPIDs()
    Dim Result: Set Result = CreateObject("Scripting.Dictionary")
    Dim Process
    For Each Process In WMI.ExecQuery("SELECT ProcessId FROM Win32_Process")
        Result(Process.ProcessId) = 1
    Next
    Set GetRunningPIDs = Result
End Function

Function WaitForNewPID(PidsBefore, TimeoutSeconds)
    Dim i, Process
    For i = 1 To TimeoutSeconds * 4
        WScript.Sleep 250
        For Each Process In WMI.ExecQuery("SELECT ProcessId FROM Win32_Process")
            If Not PidsBefore.Exists(Process.ProcessId) Then
                WaitForNewPID = Process.ProcessId
                Exit Function
            End If
        Next
    Next
    WaitForNewPID = -1
End Function

Sub WaitForPIDExit(PID)
    Dim Running
    Do
        WScript.Sleep 500
        Running = False
        Dim Process
        For Each Process In WMI.ExecQuery("SELECT ProcessId FROM Win32_Process WHERE ProcessId=" & PID)
            Running = True
        Next
    Loop While Running
End Sub

Function GetPassedDocIdAndType()
    Dim ArgObj: Set ArgObj = WScript.Arguments
    Dim IniFilePath: IniFilePath = ArgObj(0)
    Dim osParamFile: Set osParamFile = FSO.OpenTextFile(IniFilePath)
    Dim IniFileContent: IniFileContent = osParamFile.ReadAll
    osParamFile.Close
    FSO.DeleteFile IniFilePath, True
    Dim DocIDs: DocIDs = Split(IniFileContent, vbCrLf)
    Dim DocIdAndType: DocIdAndType = Split(DocIDs(0), ",")
    Dim Result: Set Result = CreateObject("Scripting.Dictionary")
    Result("id") = DocIdAndType(0)
    Result("type") = DocIdAndType(1)
    Set GetPassedDocIdAndType = Result
End Function

Wenig überraschend ist aber auch dies weit weg von perfekt. Viele Anwendungen werden heute nicht zur Bearbeitung eines Dokuments geöffnet und dann wieder geschlossen. Vielmehr öffnet die gleiche Anwendung, wenn schon offen, weitere Dateien in Reitern, weiteren Fenstern etc. Dies tun Word, Excel, aber auch LibreOffice und viele andere eben genau so.

Sofern niemand hier eine andere, allgemeine Lösung kennt, muss man jede externe Anwendung und jedes Dateiformat separat betrachten und testen.

1 „Gefällt mir“