rubberman
Goto Top

VBScript setzt Umgebungsvariable - Verarbeitung im Batch funktioniert nur bedingt

Hallo Forum,
ich versuche mich gerade an einem VBS-Tool, das die Zeit vom Start des Tools in Sekunden hochzählt und in eine Umgebungsvariable schreibt. Das ganze hat den Sinn, die Laufzeit von einzelnen (langwierigeren) Batchsteps anzuzeigen.

Grundsätzlich funktioniert das ganze auch hervorragend. Beim ersten Start wird ein temporäres VBScript in Temp geschrieben, das die Arbeit übernimmt. Beim erneuten Aufruf wird dieses Script geschlossen, gelöscht und die Umgebungsvariablen gelöscht.

Hier erst einmal die Datei secdiff.vbs:
timev = Now
Dim oSh, oEnv
Set oSh = CreateObject("WScript.Shell")  
Set oEnv = oSh.Environment("volatile")  
temp = osh.ExpandEnvironmentStrings("%temp%")  
FileName = temp & "\secdifftemp.vbs"  
If oEnv("secdiff") = "" Then  
  oEnv("secdiff") = 0  
  vbsContents = "Dim oSh, oEnv" & vbCrLf  
  vbsContents = vbsContents & "Set oSh = CreateObject(""WScript.Shell"")" & vbCrLf  
  vbsContents = vbsContents & "Set oEnv = oSh.Environment(""volatile"")" & vbCrLf  
  vbsContents = vbsContents & "Set oSh = Nothing" & vbCrLf  
  vbsContents = vbsContents & "Do" & vbCrLf  
  vbsContents = vbsContents & "stimediff = DateDiff(""s"", WScript.Arguments(0), Now)" & vbCrLf  
  vbsContents = vbsContents & "oEnv(""secdiff"") = stimediff" & vbCrLf  
  vbsContents = vbsContents & "WScript.Sleep 500" & vbCrLf  
  vbsContents = vbsContents & "Loop"  
  Set oFile = CreateObject("Scripting.FileSystemObject").OpenTextFile(FileName, 2, True)  
  oFile.Write vbsContents
  Set oFile = Nothing
  Dim oExec
  Set oExec = oSh.Exec("wscript.exe" & " """ & FileName & """ " & """" & timev & """")  
  secdifftempID = oExec.ProcessID
  Set oExec = Nothing
  oEnv("secdifftempID") = secdifftempID  
Else
  secdifftempID = oEnv("secdifftempID")  
  Dim results
  Set results = GetObject("winmgmts:\\.\root\cimv2").ExecQuery("Select * from Win32_Process where ProcessId = '" & secdifftempID & "'")  
  For Each obj In results
    obj.Terminate()
  Next
  Set results = Nothing  
  WScript.Sleep 200
  oEnv.Remove "secdiff"  
  oEnv.Remove "secdifftempID"  
  Dim oFs
  Set oFs = CreateObject("Scripting.FileSystemObject")  
  oFs.GetFile(FileName).Delete
  Set oFs = Nothing
End If
Set oEnv = Nothing
Set oSh = Nothing

Wie gesagt, das Script macht seine Arbeit gut. Wenn ich es händisch starte und anschließend die Variable %secdiff% in einem Batch abfrage, erhalte ich die verstrichene Zeit in Sekunden.

Das Problem liegt darin, dass bei mehrmaligem Aufruf der Variablen %secdiff% zu Laufzeit des Batch, keine Änderung des Werts zu beobachten ist. Es scheint als würde die Variable nur einmal expandiert.

Beispiel *.bat:
@echo off &setlocal
echo %secdiff%
secdiff
echo %secdiff%
ping -n 6 localhost>nul
echo %secdiff%
secdiff
echo %secdiff%
pause

Erwarten würde ich jetzt:
ECHO ist ausgeschaltet (OFF).
0
5
ECHO ist ausgeschaltet (OFF).
Drücken Sie eine beliebige Taste . . . 

Leider bleibt es durchgängig bei "ECHO ist ausgeschaltet (OFF).", obwohl das VBScript ordnungsgemäß läuft.
Was kann ich tun?

Vielen Dank im Voraus.
Grüße
rubberman

Content-Key: 122448

Url: https://administrator.de/contentid/122448

Printed on: April 19, 2024 at 19:04 o'clock

Member: Biber
Biber Aug 11, 2009 at 05:19:57 (UTC)
Goto Top
Moin rubberman,

mit dem Befehl 'Set oSh = CreateObject("WScript.Shell") ' erzeugst Du ja zwangsweise nur eine vom Parent-Prozess abgeleitete Tochter-Instanz der laufenden CMD-Session.
Bedeutet, dass für die neue instanz alle Umgebungsvariablen gelten, die auch der ober-Instanz bekannt sind.
Genau dieses nutzt Du ja auch beim Auslesen/Expandieren der %temp%-Variablen.
Dennoch steht Dir "nur" eine Kopie des gesamten Environments zur verfügung, nicht etwa das Original.
Und die Kopie kannst Du natürlich ändern, erweitern oder anpassen wiedewutt, aber Du nimmst diese geänderte Kopie mit ins virtuelle Grab, wenn der Tochterprozess beendet wird.
Ob nun mit ' Set oEnv = Nothing:.Set oSh = Nothing' oder ohne diese beiden Zeilen ist unerheblich.

Umgehungsmöglichkeit: Übergib die %secdiff%-Werte nicht als Umgebungsvariable, sondern transportiere diese Info via Wscript.echo raus aus dem VBS-Schnipsel und rein in den Batch mit einer FOR/F-Anweisung.
Dafür haben wir ein oder zwei Beispiele im Bereich "Batch & Shell".

Grüße
Biber
Member: rubberman
rubberman Aug 11, 2009 at 12:21:25 (UTC)
Goto Top
Hallo Biber,

vielen Dank für die schnelle Antwort. So ganz kann ich das ganze zwar noch nicht nachvollzierhen, da der Wert für die Environmentvariable in die Registry geschrieben wird und (dem leichten Bildschirmflackern zu Folge) auch übernommen wird.

Aber gut. Bin deinem Ratschlag nachgekommen (falls ich es nicht fasch verstanden habe) und habe den Beispielbatch entsprechend umgebaut:
Beispiel *.bat:
@echo off &setlocal

set tmpScript=%temp%\tmp.vbs
echo Set oSh = CreateObject("WScript.Shell")>"%tmpScript%"  
echo WScript.Echo osh.ExpandEnvironmentStrings("%secdiff%")>>"%tmpScript%"  
echo Set oSh = Nothing>>"%tmpScript%"  

for /f "tokens=*" %%i in ('cscript //nologo "%tmpScript%"') do echo %%i  
secdiff.vbs
for /f "tokens=*" %%i in ('cscript //nologo "%tmpScript%"') do echo %%i  
ping -n 6 localhost>nul
for /f "tokens=*" %%i in ('cscript //nologo "%tmpScript%"') do echo %%i  
secdiff.vbs
for /f "tokens=*" %%i in ('cscript //nologo "%tmpScript%"') do echo %%i  

del %tmpScript%
pause

Leider auch hier keinerlei Ausgabe.

Zur Gegenprobe:
das tmpScript in eine separate Datei gepackt:
*.vbs:
Set oSh = CreateObject("WScript.Shell")  
WScript.Echo osh.ExpandEnvironmentStrings("%secdiff%")  
Set oSh = Nothing
und anschließend die secdiff.vbs und den obigen Schnipsel händisch gestartet, ergibt eine Ausgabe. So hat es auch schon bei Versuchen mit separat gestarteten Batch-Schnipseln funktioniert, denn einnmal lässt sich auch da %secdiff% expandieren. Nur mehrfaches Erweiternen (nach Warte-Ping) ergibt keine Änderung des Wertes in der Ausgabe.

Wie ich noch anders den Wert dynamisch (irgendwann im laufenden Batch) abgreifen kann, fällt mir nicht ein.

Grüße
rubberman
Member: bastla
bastla Aug 11, 2009 at 23:16:06 (UTC)
Goto Top
Hallo rubberman und Biber!

Was spricht eigentlich gegen die Verwendung einer Datei, um den Start-Timestamp festzuhalten (eine Temporärdatei mehr sollte ja keinen großen Unterschied machen)?

Dann würde eigentlich etwas in der Art genügen:
@echo off & setlocal
set "T=%temp%\Timestamp.txt"  
set "S=%temp%\Secdiff.vbs"  
>%S% echo WScript.Echo DateDiff("s",CreateObject("Scripting.FileSystemObject").OpenTextFile(WScript.Arguments(0)).ReadAll,Now)  

::Start
>%T% echo %date%%time:~,8%

ping -n 6 localhost>nul
for /f %%i in ('cscript //nologo %S% %T%') do set "secdiff=%%i"  
echo %secdiff%

ping -n 10 localhost>nul
for /f %%i in ('cscript //nologo %S% %T%') do set "secdiff=%%i"  
echo %secdiff%

::Ende
del %S%
del %T%
Soferne die hier nur durch "ping"-Pausen angedeuteten Aktionen weitere Batches sind, übernehmen diese bei einem Aufruf mit "call" ja die Variablen %S% und %T% (der Inhalt Letzterer könnte als zusätzliche Vereinfachung gleich in das VBScript als Konstante eingetragen werden), und es "kostet" also jeweils nur die eine "for /f"-Zeile im aufgerufenen Batch, um die (aktualisierte) Variable %secdiff% auch dort zu erhalten ...

Grüße
bastla
Member: rubberman
rubberman Aug 11, 2009 at 23:47:18 (UTC)
Goto Top
Hallo bastla,

perfekt. Und ich habe (Asche aufs Köpfchen) wieder mal viel zu kompliziert gedacht. face-smile
Bin wieder mal überrascht, was man so alles in eine VBS-Zeile packen kann.

Vielen Dank!

Falls doch noch jemand weiß, wie mit selbstgebauten Umgebungsvariablen umzugehen ist, oder was das OS mit dynamischen Variablen wie %date%, %time% oder %random% veranstaltet und ob man das per Script nachstellen kann, würde mich das nach der ganzen Probiererei schon interessieren.

Nochmals danke und Grüße
rubberman

EDIT:
@Biber,

habe mir natürlich auch Dein Posting noch mal intensiv zu Gemüte geführt.
Nach meinen Versuchen scheint es mir eher so, dass ich das "Original" des Environments ändere, sich aber die "Kopie" in der geöffneten CMD-Instanz nicht aktualisiert wird. Um so interessanter, wie das etwa bei %time% trotzdem funktioniert... [ Man(n) will ja was lernen face-wink ]
Mitglied: 76109
76109 Aug 12, 2009 at 19:52:05 (UTC)
Goto Top
Hallo rubberman!

Was die Umgebungsvariablen angeht, liegst Du falsch.

Das Master-Environ (1.Instanz) kannst Du z.B. auf der Console nur mit einem direkten SET-Befehl ändern. Sobald eine Batch, ein Programm etc. aufgerufen wird, steht immer nur eine Environ-Kopie des Aufrufers zur Verfügung. D.h. im Grunde wird das Environ von Instanz zu Instanz vererbt und wenn eine Instanz beendet wird, existiert die jeweilige Environ-Kopie nicht mehr.

Das kannst Du ganz einfach testen, indem Du z.B. auf der Console Command oder Cmd eingibst und eine SET-Variable definierst und diesen Vorgang mehrmals wiederholst und am Ende wieder mit dem Befehl Exit von der jeweils letzten Instanz in die vorige Instanz wechselst.

Den Grund dafür ist, dass das Environ pro Instanz maximal 32KB groß sein darf, aber nur einen Speicherblock mit der aktuellen Größe belegt. Das wurde mal in DOS so festgelegt und wurde zwecks Kompatibilität so aufrecht erhalten.

Beim setzen einer oder mehreren Variablen mit SET, muss im Prinzip ein neuer Speicherblock angelegt werden und hier fangen die Probleme an. Wenn ein Programm geladen wird, dann benötigt es bestimmte Informationen. D.h. einem Programm wird eine sogenannte Programm-Segemnt-Präfix (PSP) vorangestellt. Bei einer *.COM währen das z.B. die ersten 256 Byte zusätzlich zum eigentlichen Programmcode. In der PSP befindet sich dann u.a. die Adresse des Environ-Speicherblocks.

Wenn jetzt z.B. ein Programm geladen wird, das wiederum ein anderes Programm oder eine Command-Funktion aufruft, müsste die PSP aller bereits geladenen Programme neu initialisiert werden und das währe wiederum schwierig, da z.B. eine *.EXE noch einen unterschiedlich großen Header mit Adressen für die Speichermodell-Initialisierung enthält....

Command/Cmd-Beispiel:
Set Instanz_1=1
Command
Set Instanz_2=2
Command
Set Instanz_3=3

Ergebnis:
Set -> Instanz_1=1 + Instanz_2=2 + Instanz_3=3
Exit
Set -> Instanz_1=1 + Instanz_2=2
Exit
Set -> Instanz_1=1

Die Variablen %Date% und %Time% und %Random% werden von der Cmd direkt erzeugt und haben mit dem Environ nix am Hutface-smile

Gruß Dieter
Member: rubberman
rubberman Aug 12, 2009 at 20:29:38 (UTC)
Goto Top
Hallo didi1954,

stimme völlig Deiner Erklärung zu.
Die Sache ist nur, dass ich mit einem autark laufenden VBScript das Environment geändert habe (also den entsprechenden Registry-Eintrag). Dass sich diese Änderungen auch wirksam auf das Masterenvironment ausgewirkt haben, haben Tests mit separat aufgerufenen Batches oder auch VBScripts bewiesen. Da in der entsprechenden Instanz aber nur ein zum Zeitpunkt des Aufrufs geltendes Abbild des Environs gilt, hatte ich gehofft dieses Abbild zur Laufzeit aktualisieren zu können. Scheint offensichtlich nicht möglich zu sein.
Da ich nun auch weiß, wie %date%, %time% und %random% zustande kommen, bin ich wieder ein gutes Stück klüger, und weiß dass ich ähnliche Konzepte zukünftig gleich knüllen kann face-wink

Vielen Dank und Grüße
rubberman