Batch in vorletzte Zeile einer Datei schreiben
Moin zusammen,
ich habe ein Script gebastelt, dass für die Personalabteilung die Logins der Mitarbeiter an ihren PC per Autostartscript erfasst, damit die Abteilung einen schnellen Überblick hat welche Mitarbeiter nicht anwesend waren. (Keine Zeiterfassung vorhanden die das regelt).
Das ganze soll die Daten in eine XML schreiben und diese wird dann per Javascript in einer HTML Datei in einer Table ausgegeben.
Die XML soll wie folgt aufgebaut sein:
Beim ausführen der Batch wird erst mal geprüft ob die Datei bereits existiert, falls nicht werden die ersten beiden Zeilen gesetzt.
Danach wird der Datensatz selbst geschrieben und die XML mit </DATEN> geschlossen.
Nun soll das schließende </DATEN> Tag ja nur einmal in der XML vorkommen und zwar ganz am Schluss.
Wenn ich die Batch aber ausführe wird das logischerweise jedes mal erneut in die Datei geschrieben.
Also so:
(Zeile 8 ist hier der unerwünschte Teil)
Was ich nun bräuchte ist entweder die Möglichkeit, dass mir die Batch nicht an das Ende der Datei einen neuen Datensatz schreibt sondern in die Vorletzte Zeile und in dem Fall prüft ob </DATEN> bereits vorhanden ist. Falls ja soll das nicht erneut in die Datei geschrieben werden.
Alternativ würde auch ein löschen der letzten Zeile gehen damit diese dann neu geschrieben werden kann.
Hier noch der aktuelle Batch code:
Kann mir da wer helfen?
Danke
Gruß Bem
ich habe ein Script gebastelt, dass für die Personalabteilung die Logins der Mitarbeiter an ihren PC per Autostartscript erfasst, damit die Abteilung einen schnellen Überblick hat welche Mitarbeiter nicht anwesend waren. (Keine Zeiterfassung vorhanden die das regelt).
Das ganze soll die Daten in eine XML schreiben und diese wird dann per Javascript in einer HTML Datei in einer Table ausgegeben.
Die XML soll wie folgt aufgebaut sein:
<?xml version="1.0" encoding="UTF-8"?>
<DATEN>
<DATENSATZ>
<MITARBEITER>Max Mustermann</MITARBEITER>
<LOGINNAME>m.mustermann</LOGINNAME>
<UHRZEIT> 12:34:56</UHRZEIT>
</DATENSATZ>
<DATENSATZ>
<MITARBEITER>Erika Mustermann</MITARBEITER>
<LOGINNAME>e.mustermann</LOGINNAME>
<UHRZEIT> 01:23:45</UHRZEIT>
</DATENSATZ>
</DATEN>
Beim ausführen der Batch wird erst mal geprüft ob die Datei bereits existiert, falls nicht werden die ersten beiden Zeilen gesetzt.
Danach wird der Datensatz selbst geschrieben und die XML mit </DATEN> geschlossen.
Nun soll das schließende </DATEN> Tag ja nur einmal in der XML vorkommen und zwar ganz am Schluss.
Wenn ich die Batch aber ausführe wird das logischerweise jedes mal erneut in die Datei geschrieben.
Also so:
<?xml version="1.0" encoding="UTF-8"?>
<DATEN>
<DATENSATZ>
<MITARBEITER>Max Mustermann</MITARBEITER>
<LOGINNAME>m.mustermann</LOGINNAME>
<UHRZEIT> 12:34:56</UHRZEIT>
</DATENSATZ>
</DATEN>
<DATENSATZ>
<MITARBEITER>Erika Mustermann</MITARBEITER>
<LOGINNAME>e.mustermann</LOGINNAME>
<UHRZEIT> 01:23:45</UHRZEIT>
</DATENSATZ>
</DATEN>
(Zeile 8 ist hier der unerwünschte Teil)
Was ich nun bräuchte ist entweder die Möglichkeit, dass mir die Batch nicht an das Ende der Datei einen neuen Datensatz schreibt sondern in die Vorletzte Zeile und in dem Fall prüft ob </DATEN> bereits vorhanden ist. Falls ja soll das nicht erneut in die Datei geschrieben werden.
Alternativ würde auch ein löschen der letzten Zeile gehen damit diese dann neu geschrieben werden kann.
Hier noch der aktuelle Batch code:
if not exist "Login-%date%.xml" (
echo ^<?xml version="1.0" encoding="UTF-8"?^> >> Login-%date%.xml
echo ^<DATEN^> >> Login-%date%.xml"
echo ^<DATENSATZ^> >> Login-%date%.xml)
end
if "%userdomain%" neq "%computername%" set "dom=/domain"
for /f "tokens=2*" %%i in ('net user "%username%" %dom%^|findstr "Vollst"') do echo ^<MITARBEITER^>%%j^</MITARBEITER^> >> Login-%date%.xml
end
echo ^<LOGINNAME^>%username%^</LOGINNAME^> >> Login-%date%.xml
echo ^<UHRZEIT^>%time:~0,8%^</UHRZEIT^> >> Login-%date%.xml
echo ^</DATENSATZ^> >> Login-%date%.xml
echo ^</DATEN^> >> Login-%date%.xml
Kann mir da wer helfen?
Danke
Gruß Bem
Bitte markiere auch die Kommentare, die zur Lösung des Beitrags beigetragen haben
Content-ID: 452705
Url: https://administrator.de/forum/batch-in-vorletzte-zeile-einer-datei-schreiben-452705.html
Ausgedruckt am: 04.04.2025 um 04:04 Uhr
19 Kommentare
Neuester Kommentar
Moin,
weiß Euer Betriebsrat von dem Ansinnen und hat dem zugestimmt?
Ansonsten nimm die Powershell, da die mit XML umgehen kann. Hier ist schön beschrieben, wie das geht:
https://www.langlitz-it.de/?p=1297
hth
Erik
weiß Euer Betriebsrat von dem Ansinnen und hat dem zugestimmt?
Ansonsten nimm die Powershell, da die mit XML umgehen kann. Hier ist schön beschrieben, wie das geht:
https://www.langlitz-it.de/?p=1297
hth
Erik
Moin,
Dann muss wohl jeder einzelne Mitarbeiter zustimmen.
Hmm möglich, nur bin ich nicht sicher ob das an diesem Punkt noch viel Sinn macht, da das Script ansonsten fast fertig ist.
Wenn ich das jetzt nochmal in PS umschreibe gehen hier wohl nochmal 1-2 Stunden ins Land bis ich das geschrieben habe. z.B. die Syntax zur Ausgabe des Anzeigenamens aus der AD ist natürlich eine ganz andere.
Das ist nicht wirklich schwer. Das Skript setzt voraus, dass die Datei existiert.
That's it. Hat keine zwei Stunden gedauert.
hth
Erik
Dann muss wohl jeder einzelne Mitarbeiter zustimmen.
Ansonsten nimm die Powershell, da die mit XML umgehen kann. Hier ist schön beschrieben, wie das geht:
https://www.langlitz-it.de/?p=1297
https://www.langlitz-it.de/?p=1297
Hmm möglich, nur bin ich nicht sicher ob das an diesem Punkt noch viel Sinn macht, da das Script ansonsten fast fertig ist.
Wenn ich das jetzt nochmal in PS umschreibe gehen hier wohl nochmal 1-2 Stunden ins Land bis ich das geschrieben habe. z.B. die Syntax zur Ausgabe des Anzeigenamens aus der AD ist natürlich eine ganz andere.
Das ist nicht wirklich schwer. Das Skript setzt voraus, dass die Datei existiert.
# Datei einlesen
[xml]$xml = get-content e:\datei.xml
# Neuen Datensatz erzeugen
$new_ds = $xml.CreateElement("Datensatz")
# Edit: Du wolltest ja auch noch den Klarnamen haben
$new_name = $xml.CreateElement("Mitarbeiter")
$new_name.set_InnerText($(get-aduser $env:username).name)
# Neuen Mitarbeiter erzeugen
$new_login = $xml.CreateElement("Loginname")
# Mitarbeiter mit Loginnamen füllen
$new_login.set_innerText($env:USERNAME)
# Neues Datum erzeugen
$new_date = $xml.CreateElement("Uhrzeit")
# Datum mit aktueller Zeit füllen
$new_date.set_InnerText($(get-date))
# Daten an Datensatz anhängen
$new_ds.AppendChild($new_name)
$new_ds.AppendChild($new_login)
$new_ds.AppendChild($new_date)
# Datensatz in die Datei einfügen
$xml.daten.AppendChild($new_ds)
# Datei speichern
$xml.Save("e:\datei.xml")
That's it. Hat keine zwei Stunden gedauert.
hth
Erik
Heut ist nix los. Hier das Skript nochmal mit Erzeugen der Datei, wenn sie nicht exisistiert und Datum im Dateinamen:
# Datei einlesen
if(!$(test-path e:\login-$(get-date -format "yyMMdd").xml)) {
[xml]$xml = '<?xml version="1.0" encoding="UTF-8"?> <DATEN><HEAD>Mitarbeiterlogin</HEAD></DATEN>'
}
else {
[xml]$xml = get-content e:\daten1tb\test\login-$(get-date -format "yyMMdd").xml
}
# Neuen Datensatz erzeugen
$new_ds = $xml.CreateElement("Datensatz")
# Edit: Du wolltest ja auch noch den Klarnamen haben
$new_name = $xml.CreateElement("Mitarbeiter")
$new_name.set_InnerText($(get-aduser $env:username).name)
# Neuen Mitarbeiter erzeugen
$new_login = $xml.CreateElement("Loginname")
# Mitarbeiter mit Loginnamen füllen
$new_login.set_innerText($env:USERNAME)
# Neues Datum erzeugen
$new_date = $xml.CreateElement("Uhrzeit")
# Datum mit aktueller Zeit füllen
$new_date.set_InnerText($(get-date))
# Daten an Datensatz anhängen
$new_ds.AppendChild($new_name)
$new_ds.AppendChild($new_login)
$new_ds.AppendChild($new_date)
# Datensatz in die Datei einfügen
$xml.daten.AppendChild($new_ds)
# Datei speichern
$xml.Save("e:\login-$(get-date -format "yyMMdd").xml")
Zitat von @Bem0815:
get-content : Der Pfad "C:\test\datei.xml" kann nicht gefunden werden, da er nicht vorhanden ist.
> In C:\test\test.ps1:2 Zeichen:13
> + [xml]$xml = get-content c:\test\datei.xml
> + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> + CategoryInfo : ObjectNotFound: (C:\test\datei.xml:String) [Get-Content], ItemNotFoundException
> + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Die Datei ist nicht am angegebenen Pfad. Alles andere sind Folgefehler. Ich habe das Skript getestet.
get-aduser : Die Benennung "get-aduser" wurde nicht als Name eines Cmdlet, einer Funktion, einer Skriptdatei oder eines ausführbaren Programms erkannt. Überprüfen Sie die Schreibweise des
> Namens, oder ob der Pfad korrekt ist (sofern enthalten), und wiederholen Sie den Vorgang.
> In C:\test\test.ps1:9 Zeichen:27
> + $new_name.set_InnerText($(get-aduser $env:unsername).name)
> + ~~~~~~~~~~
> + CategoryInfo : ObjectNotFound: (get-aduser:String) , CommandNotFoundException
> + FullyQualifiedErrorId : CommandNotFoundException
Kein AD vorhanden? Sind die RSAT installiert (zumindest die Powershell-Module)? Der Rest sind wieder Folgefehler.
Das Skript mit Erzeugen der Datei habe ich ja schon gepostet.
hth
Erik

fällt dir ggfs. noch eine Alternative ein den Anzeigenamen des Domänenbenutzers über PS auszulesen?
AD Tools sind überflüssig, dazu nimmt man einfach einen ADSISEARCHER der ist in jeder Powershell per Default verfügbar:$new_name.InnerText = ([adsisearcher]"(SamAccountName=$env:USERNAME)").FindOne().Properties.displayname
Jo, auch das geht mit dem ADSI-Connector:
Das stellst Du vor das $new_name.set_Innertext und ersetzt das $(get-aduser ...) durch $displayname.
$sam = $env:USERNAME
$root = [ADSI]''
$search = New-Object System.DirectoryServices.DirectorySearcher($root)
$search.filter = "(sAMAccountName=$sam)"
$user = $search.FindAll()
$displayname = ($user.properties).name
Das stellst Du vor das $new_name.set_Innertext und ersetzt das $(get-aduser ...) durch $displayname.
Zitat von @139708:
fällt dir ggfs. noch eine Alternative ein den Anzeigenamen des Domänenbenutzers über PS auszulesen?
AD Tools sind überflüssig, dazu nimmt man einfach einen ADSISEARCHER der ist in jeder Powershell per Default verfügbar:> $new_name.InnerText = ([adsisearcher]"(SamAccountName=$env:USERNAME)").FindOne().Properties.displayname
>
Noch einfacher.

Es sei dir aber noch gesagt das das gleichzeitige Schreiben in eine einzige Datei zu Fehlern führt wenn zwei Systeme gleichzeitig versuchen das File zu ersetzen. D.h. System 1 schreibt das File gerade neu, während System2 das auch versucht, dann wird der Login-Eintrag von System2 nicht vorhanden sein weil der Schreibvorgang fehlschlägt. Es sollte also zumindest noch eine Lock-Abfrage eingefügt und solange gewartet werden bis das File wieder neu geschrieben werden kann!
Besser wäre es hier gleich ein Datenbank-Backend für die Transactions zu verwenden.
Besser wäre es hier gleich ein Datenbank-Backend für die Transactions zu verwenden.
Zitat von @139708:
Es sei dir aber noch gesagt das das gleichzeitige Schreiben in eine einzige Datei zu Fehlern führt wenn zwei Systeme gleichzeitig versuchen das File zu ersetzen. D.h. System 1 schreibt das File gerade neu, während System2 das auch versucht, dann wird der Login-Eintrag von System2 nicht vorhanden sein weil der Schreibvorgang fehlschlägt. Es sollte also zumindest noch eine Lock-Abfrage eingefügt und solange gewartet werden bis das File wieder neu geschrieben werden kann!
Besser wäre es hier gleich ein Datenbank-Backend für die Transactions zu verwenden.
Es sei dir aber noch gesagt das das gleichzeitige Schreiben in eine einzige Datei zu Fehlern führt wenn zwei Systeme gleichzeitig versuchen das File zu ersetzen. D.h. System 1 schreibt das File gerade neu, während System2 das auch versucht, dann wird der Login-Eintrag von System2 nicht vorhanden sein weil der Schreibvorgang fehlschlägt. Es sollte also zumindest noch eine Lock-Abfrage eingefügt und solange gewartet werden bis das File wieder neu geschrieben werden kann!
Besser wäre es hier gleich ein Datenbank-Backend für die Transactions zu verwenden.
Da hast Du vollkommen recht. Das könnte man so lösen:
while($testpath "login-lock.txt") {
# Die Schleife dient nur dazu, dass nicht weiter gemacht wird
}
out-file login-lock.txt -inputobject "locked"
# Hier der Rest des Skripts
# Letzte Zeile
remove-item "login-lock.txt"
Was mir daran aber nicht wirklich gefällt, ist, dass die User alle auf den Ordner und die Dateien Schreibzugriff brauchen.

Das könnte man so lösen:
Dafür braucht es kein extra File, es reicht mit der System.IO.File Open Methode mit Schreibzugriff auf das File in einem try catch Konstrukt zuzugreifen. Wird eine entsprechende Exception geworfen weis man das das File gerade in Benutzung ist, das ganze in einer While Schleife.Vorzuziehen ist aber weiterhin eine DB Instanz wenn Konsistenz gefordert ist.
Zitat von @139708:
Vorzuziehen ist aber weiterhin eine DB Instanz wenn Konsistenz gefordert ist.
Das könnte man so lösen:
Dafür braucht es kein extra File, es reicht mit der System.IO Open Methode mit Schreibzugriff auf das File in einem try catch Konstrukt zuzugreifen. Wird eine entsprechende Exception geworfen weis man das das File gerade in Benutzung ist, das ganze in einer While Schleife.Vorzuziehen ist aber weiterhin eine DB Instanz wenn Konsistenz gefordert ist.
Auf jeden Fall.
Nein! Ob die Datei in Benutzung ist oder nicht, musst Du egal wie vor dem Auslesen ganz am Anfang testen. Sonst öffnest Du sie im Zustand mit z. B. Meier, Müller, Schulze. Kurz darauf öffnet eine andere Instanz die Datei auch in diesem Zustand. Die erste Instanz schreibt Hansen mit rein und speichert. Die zweite Instanz kann jetzt nicht speichern, weil die Lock-Datei exisitiert. Die erste löscht die Lock-Datei wieder. Jetzt darf die zweite schreiben und schreibt Petersen rein. Sie hat aber die Datei noch in dem Zustand ohne Hansen. Also überschreibt sie die gerade geschriebene Datei wieder und Hansen hat nicht gearbeitet.

Hier optimierter Code für die Prüfung ob ein File im Zugriff ist, mit zus. 20s Abbruchbedingung damit das Ding nicht ewig hängt.
$file = 'C:\test.xml'
function Is-FileLocked([string]$filePath){
try{
$fileStream = (New-Object System.IO.FileInfo $filePath).OpenWrite()
# file is not locked by another process
return $false
}catch {
# file is locked
return $true
}finally{
if($filestream){
$fileStream.Close(); $fileStream.Dispose()
}
}
}
$maxtries = 20
$tries = 0
while ($tries -lt $maxtries -and (Is-FileLocked $file)){$tries++;sleep 1}
if ($tries -ge $maxtries){
write-error -Message "File could not bee opened for writing!" -Category LimitsExceeded
exit 1
}