Ich habe bereits vor einigen Monaten mal über die Erkennung von laufenden Prozessen via Batch geschrieben. Damals habe ich diese Erkennung in einfachen oder komplexeren If Else Blöcken verschachtelt. Ich habe nun gemerkt, dass dies zu Problemen und falschen Erkennungen führen kann!

Daher hier das Update: If Else Verschachtelungen können zwar ein paar Zeilen sparen aber verfälschen Errorlevel Rückgaben! In If Else Blöcken würde ich keine Programmbefehle schreiben, die mit dem Errorlevel arbeiten sollen. Lieber mit einigen gotos arbeiten.

Hier ein Beispiel:

REM Statt:
if /i %action%==start-server (
 tasklist |find /i "IQB_Server.exe"
 if not %errorlevel%==0 start IQB_Server.exe
 goto end
)

REM folgenden Code verwenden:
if /i %action%==start-server goto start-server
:start-server
 tasklist |find /i "IQB_Server.exe"
 if not %errorlevel%==0 start IQB_Server.exe
 goto end

REM Errorlevel in IF ELSE Blöcken können falsch sein!

Nochmal zum Erkennen von Prozessen via Batch: hier sind 3 verschiedene funktionierende Methoden:

REM %errorlevel% ist immer 0 wenn der Prozess existiert
REM 1 wenn nicht gefunden
REM am besten mit 'if not "%errorlevel%"=="0"' überprüfen

REM Variante 1:
tasklist | find /i "dropbox.exe"

REM Variante 2:
tasklist /FI "IMAGENAME eq dropbox.exe" 2>NUL | find /I /N "dropbox.exe">NUL
REM beide NUL Umleitungen optional

REM Variante 3:
tasklist /nh /fi "imagename eq dropbox.exe" | find /i "dropbox.exe" >NUL
REM letzte NUL Umleitung optional

English Version

A few months ago I already wrote a post about the detection of running processes with batch. In this post I used this detection code inside simple or more complex if-else block statements. I recently noticed possible false positives if doing so. Using %errorlevel% return codes inside if-else statements can lead to wrong return codes.

So, here’s the update: if else structures can save a few code lines but can also falsify errorlevel return codes! And since the detection of running processes rely on errorlevel returns you should use gotos instead of if else.

Here’s a code example:

REM instead of:
if /i %action%==start-server (
 tasklist |find /i "IQB_Server.exe"
 if not %errorlevel%==0 start IQB_Server.exe
 goto end
)

REM use this code:
if /i %action%==start-server goto start-server
:start-server
 tasklist |find /i "IQB_Server.exe"
 if not %errorlevel%==0 start IQB_Server.exe
 goto end

REM errorlevel return codes in if-else blocks may be wrong!

Here are 3 different working code examples for process detection:

REM %errorlevel% is always 0 if the process is running
REM and 1 if it is not running
REM best way is to check with 'if not "%errorlevel%"=="0"'

REM code 1:
tasklist | find /i "dropbox.exe"

REM code 2:
tasklist /FI "IMAGENAME eq dropbox.exe" 2>NUL | find /I /N "dropbox.exe">NUL
REM both NUL redirections are optional

REM code 3:
tasklist /nh /fi "imagename eq dropbox.exe" | find /i "dropbox.exe" >NUL
REM last NUL redirection is optional

via, via

Das Ziel sind Screenshots von Webseiten, mit einem Batch Script automatisiert und kontrolliert. Keep it simple & stupid.

Die Wichtigste ist natürlich das Anfertigen der Screenshots. Das ließe sich vermutlich auch mit pure Batch erledigen, ich fand allerdings folgende Utilities besonders einfach und optimal für diesen Zweck: CutyCapt und IECapt. Beide Tools bestehen nur aus einer kleinen .exe Datei und sind kommandozeilenbasiert, optimal für eine Batch Benutzung.
CutyCapt fertigt Website Screenshots mit der WebKit Engine (Chrome und Opera nutzen diese Engine) an, IECapt nutzt die Engine des Internet Explorers.

Die Anwendung ist relativ einfach, über die CMD. Ein Beispiel:

CutyCapt.exe --url=http://www.spiegel.de/ --out=spiegel.png --min-width=1100 --min-height=800 --delay=1000

Wenn die Webseite besonders lang ist so wird sie komplett aufgenommen. Die Parameter steuern das Verhalten der Engine, beim IE gibt es nicht ganz so viele Möglichkeiten.

Das kann man jetzt in einem netten Script verpacken und schon lassen sich beliebige Webseiten mit einem Doppelklick fotografieren. Versehen mit Logging, Mailer und ein paar Kleinigkeiten, fertig.

Hier mein Script, welches 2 Screenshots der Fritz Box aufnimmt, in einem Unterordner speichert, eine Mail schickt und alles loggt, so als Muster:

Code anzeigenDen Code könnt ihr bequem mit den Links/Rechts Pfeiltasten horizontal bewegen.

@echo off
setlocal
Color 9f

REM ### INFORMATIONS ############################################

REM ****************************************************
REM  Title:   	Website Screenshot Daemon (WSD)
REM  Author:  	Hannes Schurig
REM  Created: 	22.02.2013
REM  Changed: 	25.02.2013
REM  Version: 	0.3.5
REM  Changelog: 0.1: complete mailer function with flexible callback
REM				0.1.1: complete new mailer with call, parameters and no local variables, 50% less code
REM				0.2: CutyCapt configured to take 2 screenshots, formatted datetime filename
REM				0.2.1: collect errorlevels and added to mail text
REM				0.2.2: save screenshots in subfolder, check if exist, create if needed
REM				0.3: use working directory and absolute paths for sheduling compatibility
REM				0.3.1: script renamed, detect working directory and filename
REM				0.3.2: removed usesubdir variable, remove 40-45 and edit 51 & 53 for no subdir
REM				0.3.3: fixed bug - spaces in working path lead to script malfunction
REM				0.3.4: CutyCapt parameters as variable - they do not change
REM				0.3.5: added 20 seconds max-wait parameter
REM  Credits: CutyCapt website screenshot utility: http://cutycapt.sourceforge.net/ - awesome!
REM ****************************************************

REM ### VARIABLES ############################################

for %%a in (%0) do set filename=%%~nxa
for %%a in (%0) do set wd=%%~dpa
REM remove last "\" or "/" from working directory (wd) path
if "%wd:~-1%"=="\" set wd=%wd:~0,-1%
if "%wd:~-1%"=="/" set wd=%wd:~0,-1%
set subdir=%wd%\screenshots
set mailer=1
set adminmail=hannes.schurig@online.de
set log="%wd%\logfile.txt"
set cutycapt="%wd%\CutyCapt.exe"
set datetime=0
set scerrorlevel=0

REM same parameters for every screenshot? It's shorter with:
set cutycaptparameters=--min-width=1100 --min-height=800 --delay=1000 --max-wait=20000

REM ### PROGRAM START ############################################

echo %date% - %time:~0,5% (i) WSD starting... >> %log%

REM check and create subdir if wanted and needed
if not "%subdir%"=="" (
	if not exist "%subdir%" (
		md "%subdir%"
	)
)

REM prepare short datetime with . instead :
set datetime=%date%-%time:~0,5%
set datetime=%datetime::=.%

%cutycapt% --url=http://fritz.box/ --out="%subdir%/%datetime%-dashboard.png" %cutycaptparameters%
set scerrorlevel=%errorlevel%
%cutycapt% --url=http://fritz.box/system/syslog.lua --out="%subdir%/%datetime%-events.png" %cutycaptparameters%
set scerrorlevel=%scerrorlevel%%errorlevel%

echo %date% - %time:~0,5% (i) Screenshot taken with errorlevel %scerrorlevel% >> %log%

echo %date% - %time:~0,5% (i) Sending admin mail... >> %log%
if %mailer%==1 call :mailer "WSD" "Status" "noreply@WSD.com" "Script finished with errorlevel %scerrorlevel%"

:shutdown
echo %date% - %time:~0,5% (i) WSD shutting down... >> %log%
goto end


REM ### MAILER FUNCTION ############################################

:mailer
start iexplore "http://php.webmailer.de/webmail.php&mailto=%adminmail%&contactname=%~1&subject=%~2&email=%~3&message=%~4"
REM wait and close
ping 127.0.0.1 -n 5 > nul
taskkill /im "iexplore.exe" /f
ping 127.0.0.1 -n 3 > nul

REM ### PROGRAM EXIT ############################################

:end
endlocal

Anmerkungen dazu:
Keine Unterordner? Löscht Zeile 49-54 und passt den „–out“ Parameter in Zeile 60 und 62 an. Wenn doch, passt Zeile 28 an.
IECapt statt CutyCapt? In Zeile 38 den Dateinamen anpassen.
Der Mailer muss natürlich angepasst werden. Habt ihr einen PHP Webmailer? Dann passt die Zeilen 36, 68 und 78 an. Wenn ihr keinen Mailer habt reicht es die Zeile 35 auf 0 zu setzen.
Ansonsten müsste das funktionieren.

Alternativ zu diesem Script gibt es auch Browser Plugins mit denen Website Screenshots erstellt werden können. Awesome Screenshot wäre ein Google Chrome Plugin für diesen Zweck. Die Extension fotografiert einen ausgewählten Bereich, den sichtbaren Bereich oder die komplette Seite. Das Bild lässt sich bearbeiten, speichern, teilen und mehr.
Über die Automatisierung dieser Geschichte muss man sich dann aber noch Gedanken machen, das ist vermutlich etwas schwieriger.

Bugs, Anmerkungen sowie Verbesserungsvorschläge nehme ich natürlich gerne entgegen.

Es ist wieder soweit, kürzlich veröffentlichte Mozilla die neue Version der Firefox ESR (Extended Support Release) Variante. Firefox 17.0.2 ESR ist vor allem für das Deployment in Unternehmen gedacht, wenn Stabilität und Sicherheit des Browsers wichtiger sind als neue Features und Spielereien.

Ich möchte kurz meine Verteilung hier für andere Administratoren bereitstellen.
Die Verteilung verläuft via Startscript in einer Active Directory Domäne mit 95% 32bit Windows 7 PCs.
Sie hat sich in der kompletten Firefox 10 ESR Reihe bereits als funktionstüchtig erwiesen und wurde seitdem immer wieder verbessert.

Wer sich schon etwas auskennt, hier direkt der Download:
Download section
firefox-17-esr [.zip]

Updates:
1.3 – Bug, der die Installation verhinderte, wenn kein Firefox auf dem Client installiert ist, behoben
1.4 – mozilla.cfg um 2 Einstellungen ergänzt, die 2 „Thanks for installing Firefox“ und „Congratulations on installing Firefox“ Fenster blockt

Das Paket besteht aus folgenden Dateien:
Firefox17.0.2.exe – Setup Datei
firefox-installer.bat – Setup Script
log.txt – Setup Log Datei
mozilla.cfg – Firefox Setup Config Dateien
install.ini – Firefox Setup Config Dateien
override.ini – Firefox Setup Config Dateien
local-settings.js – Firefox Browser Einstellungen, Achtung: neuer Unterordner! siehe unten
deniedPCs.txt – optional: Client Filter Config Datei
allowedPCs.txt – optional: Client Filter Config Datei
VersionCompare.exe – Versionsvergleichstool
VersionCompare-test.bat – kleines Readme- und Test Script für das Versionsvergleichstool

Das Script als Klartext zum Reinlesen:
Code anzeigenDen Code könnt ihr bequem mit den Links/Rechts Pfeiltasten horizontal bewegen.

@echo on
Color 9f
setlocal

REM *******************************************
REM  Title:   	Firefox ESR Silent Installer
REM  Author:  	Hannes Schurig
REM  Created: 	08.12.2011
REM  Changed: 	11.01.2013
REM  Version: 	1.4
REM  Quelle:	http://mockbox.net/configmgr-sccm/174-install-and-configure-firefox-silently.html
REM *******************************************

REM _______________________________________________________________
REM WICHTIG! Hier die aktuelle zu verteilende Version eingeben.
REM Die Installerdatei muss so benannt sein: Firefox[Version].exe
set newversion=17.0.2
REM _______________________________________________________________

set wd=\\server\Firefox
set toolsdir=\\server\Tools
set log=\\server\Firefox\log.txt
set instversion=0.0

REM Clientfilter: nur die Computer aus der allowedPCs.txt dürfen installieren
::for /f %%f in (%wd%\allowedPCs.txt) do if "%computername%"=="%%f" goto install
::goto end

REM Clientfilter: die Computer aus der deniedPCs.txt dürfen nicht installieren
for /f %%f in (%wd%\deniedPCs.txt) do if "%computername%"=="%%f" goto end

:install
title Firefox Installation wird überprüft...
echo Firefox Installation wird überprüft...

REM prüfe ob eine 32bit Version von Firefox schon installiert ist
for /f "tokens=1,2,3 delims= " %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\Mozilla Firefox" /v "CurrentVersion"^|findstr "CurrentVersion"') do set instversion=%%c
REM falls keine 32bit Version von Firefox gefunden wurde, prüfe 64bit
if "%instversion%"=="0.0" for /f "tokens=1,2,3 delims= " %%a in ('reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Mozilla\Mozilla Firefox" /v "CurrentVersion"^|findstr "CurrentVersion"') do set instversion=%%c

REM Version gleich?
if "%instversion%"=="%newversion%" echo %date% %time% - %computername% (%instversion%) hat bereits diese Version installiert. >> %log% & goto end

REM Wenn Version nicht gleich: vergleiche Versionen mit dem Versionchecker
%toolsdir%\VersionCompare.exe %instversion% %newversion%
set versioncompare=%errorlevel%
if %versioncompare%==0 echo %date% %time% - %computername% (%instversion%) hat bereits diese Version installiert. >> %log% & goto end
if %versioncompare%==1 echo %date% %time% - %computername% (%instversion%) hat bereits eine neuere Version installiert. >> %log% & goto end

echo %date% %time% - %computername% (%instversion%) startet die Firefox Installation...
echo %date% %time% - %computername% (%instversion%) startet die Firefox Installation... >> %log%

title Firefox wird installiert...
echo Firefox wird installiert...
REM aktuellste Version hier eintragen
"%wd%\Firefox%newversion%.exe" -ms

REM Install 32-bit customisations
if exist "%programfiles%\Mozilla Firefox\" copy /Y "%wd%\override.ini" "%programfiles%\Mozilla Firefox\"
if exist "%programfiles%\Mozilla Firefox\" copy /Y "%wd%\mozilla.cfg" "%programfiles%\Mozilla Firefox\"
if exist "%programfiles%\Mozilla Firefox\" copy /Y "%wd%\local-settings.js" "%programfiles%\Mozilla Firefox\defaults\preferences"

REM Install 64-bit customisations
if exist "%ProgramFiles(x86)%\Mozilla Firefox\" copy /Y "%wd%\override.ini" "%ProgramFiles(x86)%\Mozilla Firefox\"
if exist "%ProgramFiles(x86)%\Mozilla Firefox\" copy /Y "%wd%\mozilla.cfg" "%ProgramFiles(x86)%\Mozilla Firefox\"
if exist "%ProgramFiles(x86)%\Mozilla Firefox\" copy /Y "%wd%\local-settings.js" "%ProgramFiles(x86)%\Mozilla Firefox\defaults\preferences"


REM Removes Firefox Desktop Icon - Windows XP
::if exist "%allusersprofile%\Desktop\Mozilla Firefox.lnk" del "%allusersprofile%\Desktop\Mozilla Firefox.lnk" /S

REM Removes Firefox Desktop Icon - Windows 7
::if exist "C:\Users\Public\Desktop\Mozilla Firefox.lnk" del "C:\Users\Public\Desktop\Mozilla Firefox.lnk"

echo %date% %time% - %computername% hat die Installation abgeschlossen...
echo %date% %time% - %computername% hat die Installation abgeschlossen... >> %log%

:end
endlocal

nötige Änderungen am Script
Zeile 17: die gewünschte Version muss hier stehen, in der “korrekten” Form, wie sie auch in der Registry zu finden ist. Die Installer .exe muss ebenfalls korrekt benannt werden.
Zeile 20-22: Pfade anpassen, Zeile 20+22 müssen auf den Firefox Ordner auf eurem Server/Netzlaufwerk zeigen, Zeile 21 auf den Ordner, der die VersionCompare.exe (im Download enthalten) beinhaltet
Zeile 25-30: Clientfilter, ggf. entfernen wenn nicht gewünscht
Zeile 36-48: Versionsvergleich der installierten Version mit der verfügbaren Version, ggf. anpassen oder entfernen. Aktuell wird die 17.0.2 ESR installiert wenn auf dem PC keine oder eine ältere Version gefunden wurde, nicht aber wenn die gleiche oder eine neue (z.B. 18.0 nicht ESR) gefunden wurde.
Zeile 69-73: Löschen des Desktop Icons ist deaktiviert (Icon bleibt also), ggf. die Kommentarzeichen entfernen

Ein paar zusätzliche Informationen zur Funktionsweise und den Einstellungen findet ihr auch in meinen damaligen Firefox Deployment Posts.

Noch eine Anmerkung: Das Verzeichnis .\defaults\pref\ ist outdated! Dort gelagerte Anpassungsdateien (wie die local-settings.js) werden ignoriert. Diese Anpassungen müssen jetzt in .\defaults\preferences\ abgelegt werden. Dort werden die Änderungen auch beachtet. Von mir getestet und bestätigt. via

Die Verteilung ist getestet und funktioniert.
Ein seltsames Problem bleibt jedoch: nach der Verteilung, beim ersten Start des Firefox, wird ein Tab mit dem Hinweis „Your Firefox is out of date“ geöffnet. Und das obwohl Hilfe->Über Firefox bestätigt, dass die Version 17.0.2 im esr Channel momentan die aktuellste Version ist. Wenn jemand weiß wie ich das Anzeigen dieses Tabs verhindern kann, bitte Bescheid sagen.

Update: Diese Verteilung funktioniert übrigens auch wieder 1:1 für Thunderbird 17.0.2 ESR, gerade getestet. Einfach das Script an den 2, 3 Stellen wo „FIrefox“ zu „Thunderbird“ abändern, fertig. Die Anpassungen der mozilla.cfg müssen natürlich weg, die sind Firefox spezifisch. In der .cfg des Thunderbird hab ich aber auch nur 1 Zeile, die die globale Suche deaktiviert: pref(„mailnews.database.global.indexer.enabled“, false);

Ich traf letztens auf einen sehr spezielles Java Problem, bei dem es dann zu Problemen bei der Serialisierung von Objekten kommt.
Ziel sollte es sein eine innere Klasse zu serialisieren während die äußere Klasse eine grafische Oberfläche und mehrere Ereignishandler beinhaltet und den JFileChooser verwendet. Dies schlägt fehl.

Hier der stark vereinfachte, aber trotzdem komplett lauffähige Aufbau eines solchen fehlerhaften Programms: (Video unten)

import java.awt.Color;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import javax.swing.JFileChooser;
import javax.swing.JFrame;

public class GraphEditorGerüst extends JFrame implements MouseListener {
	
	ArrayList<Kreis> kreise = new ArrayList<Kreis>(); // globale Kreisvariable

	public GraphEditorGerüst(){
		super("GraphEditor");
		setSize(150, 100);
		addMouseListener(this);
	}
	
	public class Kreis implements Serializable {
		// innere, zu serialisierende, Klasse
		public static final long serialVersionUID = 3L;
		public int x, y, dx, dy, id, idcounter = 0;
		public Color c = Color.BLACK;	
		public Kreis(int x, int y, int dx, int dy, Color c) {
			this.x = x;
			this.y = y;
			this.dx = dx;
			this.dy = dy;
			this.c = c;
			this.id = ++idcounter;
		}
	}
	
	// Ereignishandler mouseClicked
	public void mouseClicked(MouseEvent me){
		// ein Dummykreis erstellen, der nachher geschrieben wird
		kreise.add(new Kreis(0,0,0,0,Color.BLACK));
		// neues JFileChooser Objekt
		JFileChooser fc = new JFileChooser();
		// JFileChooser Dialog anzeigen und Rückgabewert speichern
		int fcreturn = fc.showDialog(this, "Speichern");
		// wenn auf "Speichern" geklickt wurde
		if (fcreturn == JFileChooser.APPROVE_OPTION) {
			// ausgewählte Datei in ein File Objekt speichern
			File savefile = fc.getSelectedFile();
			try {
				FileOutputStream fos = new FileOutputStream(savefile);
				ObjectOutputStream oos = new ObjectOutputStream(fos);
				for (Kreis k : kreise) {
					// hier tritt der Fehler auf
					oos.writeObject(k);
				}
				oos.close();
				fos.close();
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			}
			catch (IOException e) {
				e.printStackTrace();
			}	
		}
	}	
	
	public static void main(String[] args){
		// Erzeugen eines Objektes von GraphEditorGerüst
		GraphEditorGerüst test = new GraphEditorGerüst();
		test.setVisible(true);
	}


	@Override
	public void mouseEntered(MouseEvent e) {}
	@Override
	public void mouseExited(MouseEvent e) {}
	@Override
	public void mousePressed(MouseEvent e) {}
	@Override
	public void mouseReleased(MouseEvent e) {}

}

Sieht an sich eigentlich unproblematisch aus.
Die innere Klasse soll serialisiert werden, hat das Interface implementiert und nutzt definitiv nur serialisierbare Datentypen und Elemente. Die äußere Klasse kümmert sich um die GUI und einige Ereignishandler. Einer dieser Handler enthält den JFileChooser zum Auswählen einer Datei, in die ein ObjectOutputStream nun die serialisierte innere Klasse schreiben soll. Es kommt beim Speichern, genauer gesagt bei der Ausführung von oos.writeObject, zu folgendem Fehler:

java.io.NotSerializableException: 
javax.swing.plaf.metal.MetalFileChooserUI

alertnativ auch auf

javax.swing.plaf.basic.BasicFileChooserUI$AcceptAllFileFilter

mit folgender Stack Trace:

Code anzeigenDen Code könnt ihr bequem mit den Links/Rechts Pfeiltasten horizontal bewegen.

java.io.NotSerializableException: javax.swing.plaf.metal.MetalFileChooserUI
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
	at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
	at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.writeObject(Unknown Source)
	at javax.swing.event.EventListenerList.writeObject(Unknown Source)
	at sun.reflect.GeneratedMethodAccessor6.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at java.io.ObjectStreamClass.invokeWriteObject(Unknown Source)
	at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
	at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
	at java.io.ObjectOutputStream.defaultWriteObject(Unknown Source)
	at javax.swing.JComponent.writeObject(Unknown Source)
	at sun.reflect.GeneratedMethodAccessor10.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at java.io.ObjectStreamClass.invokeWriteObject(Unknown Source)
	at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
	at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
	at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
	at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
	at java.io.ObjectOutputStream.defaultWriteObject(Unknown Source)
	at java.awt.Window.writeObject(Unknown Source)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at java.io.ObjectStreamClass.invokeWriteObject(Unknown Source)
	at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
	at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.writeObject(Unknown Source)
	at java.awt.Window.writeObject(Unknown Source)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at java.io.ObjectStreamClass.invokeWriteObject(Unknown Source)
	at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
	at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.defaultWriteFields(Unknown Source)
	at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
	at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.writeObject(Unknown Source)
	at java.util.ArrayList.writeObject(Unknown Source)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Unknown Source)
	at java.io.ObjectStreamClass.invokeWriteObject(Unknown Source)
	at java.io.ObjectOutputStream.writeSerialData(Unknown Source)
	at java.io.ObjectOutputStream.writeOrdinaryObject(Unknown Source)
	at java.io.ObjectOutputStream.writeObject0(Unknown Source)
	at java.io.ObjectOutputStream.writeObject(Unknown Source)
	at GraphEditor.actionPerformed(GraphEditor.java:191)
	at javax.swing.AbstractButton.fireActionPerformed(Unknown Source)
	at javax.swing.AbstractButton$Handler.actionPerformed(Unknown Source)
	at javax.swing.DefaultButtonModel.fireActionPerformed(Unknown Source)
	at javax.swing.DefaultButtonModel.setPressed(Unknown Source)
	at javax.swing.AbstractButton.doClick(Unknown Source)
	at javax.swing.plaf.basic.BasicMenuItemUI.doClick(Unknown Source)
	at javax.swing.plaf.basic.BasicMenuItemUI$Handler.mouseReleased(Unknown Source)
	at java.awt.Component.processMouseEvent(Unknown Source)
	at javax.swing.JComponent.processMouseEvent(Unknown Source)
	at java.awt.Component.processEvent(Unknown Source)
	at java.awt.Container.processEvent(Unknown Source)
	at java.awt.Component.dispatchEventImpl(Unknown Source)
	at java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.awt.Component.dispatchEvent(Unknown Source)
	at java.awt.LightweightDispatcher.retargetMouseEvent(Unknown Source)
	at java.awt.LightweightDispatcher.processMouseEvent(Unknown Source)
	at java.awt.LightweightDispatcher.dispatchEvent(Unknown Source)
	at java.awt.Container.dispatchEventImpl(Unknown Source)
	at java.awt.Window.dispatchEventImpl(Unknown Source)
	at java.awt.Component.dispatchEvent(Unknown Source)
	at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
	at java.awt.EventQueue.access$000(Unknown Source)
	at java.awt.EventQueue$3.run(Unknown Source)
	at java.awt.EventQueue$3.run(Unknown Source)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
	at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
	at java.awt.EventQueue$4.run(Unknown Source)
	at java.awt.EventQueue$4.run(Unknown Source)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.security.ProtectionDomain$1.doIntersectionPrivilege(Unknown Source)
	at java.awt.EventQueue.dispatchEvent(Unknown Source)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
	at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
	at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.awt.EventDispatchThread.run(Unknown Source)

Video des Beispielcodes oben:

Hier die Problemanalyse:

Das Serialisieren von nicht-statischen inneren Klassen, deren äußere Klasse Ereignisempfänger ist, ist unter gewissen Umständen nicht möglich. Es wird versucht nicht nur die innere, sondern auch die äußere Klasse zu serialisieren, da die innere Klasse nicht-statisch ist. Wenn diese äußere Klasse ereignisverarbeitende Methoden verwendet, die nicht serialisierbare Objekte (z.B. JFileChooser in ActionPerformed()) enthält, so schlägt die Serialisierung der äußeren Klasse mit der Fehlermeldung „java.io.NotSerializableException“ auf dem nicht serialisierbaren Objekt „javax.swing.plaf.metal.MetalFileChooserUI“ (im Falle des JFileChooser) fehl.

Es ergeben sich 5 Lösungsmöglichkeiten:
1.) Die innere Klasse mit dem static Modifier versehen.
2.) Kein JFileChooser verwenden.
3.) Keine ereignisverarbeitenden Methoden in der Klasse verwenden, die das zu serialisierende Objekt – oder eine innere Klasse mit diesem Objekt – enthält.
4.) Auslagern der inneren, zu serialisierenden, Klasse.
5.) Auslagern aller Ereignisempfänger und Auslagern der inneren, zu serialisierenden, Klasse.

Die erste Lösung geht wohl am schnellsten und hilft in den meisten Fällen ohne weitere Probleme zu generieren. Die zwei nächsten Lösungen schränken die Möglichkeiten ein. Die vierte Lösung ist am einfachsten umzusetzen; nur die innere, zu serialisierende, Klasse wird in eine eigene Class Datei ausgelagert und entsprechend verwendet. Die fünfte Lösung lagert zusätzlich noch alle Ereignishandler aus (was sowieso nicht schlecht ist, da es zur logischen Trennung und Übersichtlichkeit beiträgt), kann aber, je nach Größe des Projekts, relativ aufwändig werden.

Hier der Code eines komplett lauffähigen Programms, bei der die zu serialisierende Klasse ausgelagert wurde:

GraphEditor.java:

Code anzeigenDen Code könnt ihr bequem mit den Links/Rechts Pfeiltasten horizontal bewegen.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class GraphEditor extends JFrame implements MouseListener, MouseMotionListener, ActionListener {
	
	//Globale Variablen
	JTextField txt = new JTextField(10);
	JPanel panel1 = new JPanel();
	
	//Globale Variablen
	Kreis gezogenerKreis = null; //Kreis-Objekt zum Zwischenspeichern des bewegten Kreises
	
	//Kreisdurschnitt
	final int dX = 16, dY = 16;
	
	//Erstkonfiguration und Koordinatenkorrekturen
	int korrekturX = 0, korrekturY = 0;
	int korrekturX1 = 0, korrekturY1 = 0, korrekturX2 = 0, korrekturY2 = 0, configclickcounter = 1;
	boolean configured = false;
	
	//ArrayList zum Aufnehmen der Kreis-Objekte
	ArrayList<Kreis> kreise = new ArrayList<Kreis>(); 

	//Konstruktor der Klasse GraphEditor
	public GraphEditor(){
		
		//Aufruf des Konstruktors der Oberklasse
		super("GraphEditor");
		
		//Schließen des Fensters 
		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		
		//Menüleiste
		JMenuBar mbar = new JMenuBar();
		this.setJMenuBar(mbar);
		
		//Menü
		JMenu mGraph = new JMenu("Graph");
		JMenu mVertex = new JMenu("Vertex");
		
		//Menüelemente
		JMenuItem mGraphOpen = new JMenuItem("open");
		JMenuItem mGraphNew = new JMenuItem("new");
		JMenuItem mGraphSave = new JMenuItem("save");
		JMenuItem mVertexInsert	= new JMenuItem("insert");
		JMenuItem mVertexMove = new JMenuItem("move");
		JMenuItem mVertexDelete = new JMenuItem("delete");
		JMenuItem mVertexNone = new JMenuItem("none");
		
		//Hinzufügen der Elemente zur Menüleiste
		mGraph.add(mGraphNew);
		mGraph.add(mGraphOpen);
		mGraph.add(mGraphSave);
		mVertex.add(mVertexInsert);
		mVertex.add(mVertexMove);
		mVertex.add(mVertexDelete);
		mVertex.add(mVertexNone);		
		mbar.add(mGraph);
		mbar.add(mVertex);
		
		//Haupt-Panel
		this.setLayout(new BorderLayout());
		panel1.setBackground(Color.white);
		this.add(panel1, BorderLayout.CENTER);
		panel1.addMouseListener(this);
		panel1.addMouseMotionListener(this);
		
		//unteres Panel für Label und Textfeld
		JPanel panel2 = new JPanel();
		this.add(panel2, BorderLayout.SOUTH);
		
		//Label
		JLabel label = new JLabel("Selected Action", JLabel.RIGHT);
		panel2.setLayout(new GridLayout(1,2,5,0));
		panel2.add(label);
		
		//Panel für Textfeld
		JPanel panel3 = new JPanel();
		panel3.setLayout(new FlowLayout(3));
		panel2.add(panel3);
		
		//Textfeld
		txt.setText("none");
		txt.setDisabledTextColor(Color.black);
		txt.setEnabled(false);
		panel3.add(txt);
		
		//ActionListener
		mVertex.addActionListener(this); // vertex:
		mVertexInsert.addActionListener(this);
		mVertexDelete.addActionListener(this);
		mVertexMove.addActionListener(this);
		mVertexNone.addActionListener(this);
		mGraph.addActionListener(this); // graph:
		mGraphNew.addActionListener(this);
		mGraphOpen.addActionListener(this);
		mGraphSave.addActionListener(this);
		
		//ActionCommands
		mVertex.setActionCommand("vertex"); // vertex:
		mVertexInsert.setActionCommand("insert");
		mVertexDelete.setActionCommand("delete");
		mVertexMove.setActionCommand("move");
		mVertexNone.setActionCommand("none");
		mGraph.setActionCommand("graph"); // graph:
		mGraphNew.setActionCommand("new");
		mGraphOpen.setActionCommand("open");
		mGraphSave.setActionCommand("save");
	}
	
	//Überschriebene Paint-Methode zum Zeichnen der Kreise
	public void paint(Graphics g) {
		super.paint(g);
		for (Kreis k: kreise) {
			g.setColor(k.c);
			g.fillOval(k.x - k.dx/2 + korrekturX, k.y - k.dy/2 + korrekturY, k.dx, k.dy);
		}
	}
	
	//ActionEvent
	public void actionPerformed(ActionEvent ae){
		//Zwischenspeichern
		String action = ae.getActionCommand();
		
		//werden unabhängig von der Erstkonfiguration immer ausgeführt
		if(action == "delete"){
			txt.setText("Vertex: delete");
		}
		if(action == "move"){
			txt.setText("Vertex: move");
		}
		if(action == "none"){
			txt.setText("none");
		}
		//wenn insert zum ersten Mal ausgewählt wird: Infofenster anzeigen
		if (configured == false && action == "insert") {
			int wahl = JOptionPane.showConfirmDialog(this, "" +
					"Bei der ersten Verwendung des Programms muss eine Konfiguration erfolgen. \n" +
					"Bitte klicken Sie an eine Stelle mittig im Zeichenbereich und danach\n " +
					"exakt in den weißen Punkt in der Mitte des gezeichneten Kreises!", 
					"Konfiguration nötig!", JOptionPane.OK_CANCEL_OPTION);
			if (wahl == JOptionPane.OK_OPTION) {
				txt.setText("Vertex: insert");
			}
		}
		//wenn Erstkonfiguration schon abgeschlossen ist
		else if (configured == true) {
			if(action == "insert"){
				txt.setText("Vertex: insert");	
			}
		}
		
		//Löschen der Kreise aus der Liste 
		if(action == "new") {
			kreise.clear();
			repaint();
		}
		
		//Speichern der gezeichneten Kreise
		if(action == "save") {
			JFileChooser fc = new JFileChooser();
			int fcreturn = fc.showDialog(this, "Speichern");
			if (fcreturn == JFileChooser.APPROVE_OPTION) {
	            File savefile = fc.getSelectedFile();
				try {
					FileOutputStream fos = new FileOutputStream(savefile);
					ObjectOutputStream oos = new ObjectOutputStream(fos);
					for (Kreis k : kreise) {
						oos.writeObject(k);
					}
					oos.close();
					fos.close();
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				}
				catch (IOException e) {
					e.printStackTrace();
				}	
			}
		}
		
		//Öffnen einer bereits gespeicherten Datei mit Punkten
		if(action == "open") {
			kreise.clear();
			JFileChooser fc = new JFileChooser();
			int fcreturn = fc.showDialog(this, "Öffnen");
			if (fcreturn == JFileChooser.APPROVE_OPTION) {
	            File openfile = fc.getSelectedFile();
	            fc = null;
	            FileInputStream fis;
	            ObjectInputStream ois;
				try {
					fis = new FileInputStream(openfile);
					ois = new ObjectInputStream(fis);
					try {
						while (true) {
						kreise.add((Kreis) ois.readObject());
						repaint();
						}
					}
					catch (EOFException e) {
						System.out.println("Dateiende erreicht");
						ois.close();
						fis.close();
					}
				}
				catch (IOException e) {
					e.printStackTrace();
				} 
				catch (ClassNotFoundException e) {
					e.printStackTrace();
				}	
	        }
		}
		repaint();
	}
	
	//MouseEvents
	public void mouseClicked(MouseEvent me){
		repaint();
		int cursorX = me.getX();
		int cursorY = me.getY();
		//Erstkonfiguration der Koordinatenkorrektur starten, wenn noch nicht erfolgt
		if (configured == false) {
			//erster Klick der Erstkonfiguration, Textfeld mit "Vertex: insert" nur, wenn der Benutzer im Dialog "OK" geklickt hat
			if (configclickcounter == 1 && txt.getText().equals("Vertex: insert")) {
				//erstes Koordinatenpaar lesen
				korrekturX1 = me.getX();
				korrekturY1 = me.getY();
				configclickcounter++;
				/* Kreis mit den unkonfigurierten Standardkoordinaten zeichnen; 				
				 * dieser wird links oberhalb des Klickpunktes erscheinen, je nach Betriebssystem(/-version)
				 * und Windows-Design unterschiedlich!
				 * Ein kleiner weißer Kreis symbolisiert die Mitte, die angeklickt werden muss. */		 
				kreise.add(new Kreis(cursorX, cursorY, dX, dY));
				kreise.add(new Kreis(cursorX, cursorY, 2, 2, Color.WHITE));
			//zweiter Klick der Erstkonfiguration
			} else if (configclickcounter == 2 && txt.getText().equals("Vertex: insert")) {
				//Konfigurationskreise löschen
				kreise.clear();
				repaint();
				//zweites Koordinatenpaar lesen
				korrekturX2 = me.getX();
				korrekturY2 = me.getY();
				//Korrekturen errechnen
				korrekturX = korrekturX1 - korrekturX2;
				korrekturY = korrekturY1 - korrekturY2;
				//Konfiguration beenden, Infodialog anzeigen
				configured = true;
				JOptionPane.showConfirmDialog(this, "" +
						"Die Konfiguration ist abgeschlossen. \n" +
						"Koordinatenkorrektur für X: " + korrekturX + " | Y: " + korrekturY, 
						"Konfiguration abgschlossen!", JOptionPane.OK_CANCEL_OPTION);
			}
		}		
		//Erstkonfiguration beendet
		else if (configured == true){
			if(txt.getText().equals("Vertex: insert")){
				kreise.add(new Kreis(cursorX, cursorY, dX, dY));
			}
		}
		if(txt.getText().equals("Vertex: delete")){
			for(Kreis k : kreise){
				if(cursorX <= (k.x + dX/2.0) && cursorX >= (k.x - dX/2.0) && cursorY <= (k.y + dY/2.0) && cursorY >= (k.y - dY/2.0)){
					kreise.remove(k);
					break;
				} 
			}
		}
	}	
	
	public void mousePressed(MouseEvent me){
		if(txt.getText().equals("Vertex: move")){
			int cursorX = me.getX();
			int cursorY = me.getY();
			for(Kreis k : kreise){
				if(cursorX <= (k.x + dX/2.0) && cursorX >= (k.x - dX/2.0) && cursorY <= (k.y + dY/2.0) && cursorY >= (k.y - dY/2.0)){
					gezogenerKreis = k;					
				} 
			}
		}
	}
	public void mouseEntered(MouseEvent arg0){}
	public void mouseExited(MouseEvent arg0){}
	public void mouseReleased(MouseEvent arg0){
		gezogenerKreis = null;
	}

	//MouseMotionEvents
	public void mouseDragged(MouseEvent me) {
		if(txt.getText().equals("Vertex: move")){
			if(gezogenerKreis != null) {	
				gezogenerKreis.x = me.getX();
				gezogenerKreis.y = me.getY();	
				repaint();
			}
		}
	}
	public void mouseMoved(MouseEvent e){}
	
	//Main-Methode		
	public static void main(String[] args){
		
		//GraphEditor-Objekt
		GraphEditor ui = new GraphEditor();
		
		//Setzen der Größe und Sichtbarkeit des GraphEditor-Objekts
		ui.setSize(550, 350);
		ui.setVisible(true);
		
	}

}

Kreis.java:

Code anzeigenDen Code könnt ihr bequem mit den Links/Rechts Pfeiltasten horizontal bewegen.

import java.awt.Color;
import java.io.Serializable;

public class Kreis implements Serializable {
	//Innere Kreis-Klasse
	public static final long serialVersionUID = 3L;
	public int x, y, dx, dy, id;
	public Color c = Color.BLACK;	
	int counter = 0;
	// 2 verschieden überladene Konstruktoren ermöglichen die freie Farbwahl für jeden einzelnen Kreis
	public Kreis(int x, int y, int dx, int dy, Color c) {
		this.x = x;
		this.y = y;
		this.dx = dx;
		this.dy = dy;
		this.c = c;
		this.id = ++counter;
	}
	public Kreis(int x, int y, int dx, int dy) {
		this.x = x;
		this.y = y;
		this.dx = dx;
		this.dy = dy;
	}
	@Override
	public String toString() {
		return "x:"+ x + " y:" + y + " dx:" + dx + " dy:" + dy + " color:" + c.toString()+ "\n";

	}
}

Kurz zur Einleitung, weil das mein erster Post zu diesem Thema ist:
Monkeyrunner ist ein Tool des Android SDK, welches als Schnittstelle zu dem angeschlossenen Android Smartphone fungiert. Es bietet eine, mit Python Scripten angesprochene, Fernsteuerung des Geräts mit einigen Features. So lassen sich lange Interaktionsfolgen automatisiert verscripten und „unbemannt“ ausführen.

Also, Screen Lock & Unlock mit Monkeyrunner:

# simulierter Druck auf den Power Knopf -> Screen aus
device.press("POWER", MonkeyDevice.DOWN_AND_UP)

# eigene Methode zum Aufwecken des Bildschirms
# nochmal ein Druck auf POWER funktioniert nicht
device.wake()

# 1 Sekunde warten
MonkeyRunner.sleep(1)

# Ziehen des Entsperr-Rings (vom unteren Rand in die Mitte (HTC One X))
device.drag((354, 1191), (324, 525), 1, 10)

# entsperrt!

Der Sperrbildschirm ist in diesem Beispiel nur mit dem üblichen Entsperr-Ring von HTC versehen, kein Muster oder PIN.
Die einzige Hürde ist also das Festlegen der Koordinaten beim Entsperren. Dazu später noch ein weiteres Tutorial.

English Version

A short introduction first because this is the first post on this topic:

Monkeyrunner is a tool which provides an interface between your computer and the connected Android smartphone. It is controlled by Python scripts and is capable of serving as a Android device remote control with functions like sending various inputs, running apps, taking and saving screenshots from the device. This way you can automatize complex interactions and inputs and run them unattended.

How to lock & unlock the Android device with Monkeyrunner:

# simulate a POWER button press -> turn off the screen
device.press("POWER", MonkeyDevice.DOWN_AND_UP)

# monkeyrunner method to unlock the screen
# POWER button press doesn't work to turn on
device.wake()

# wait a second
MonkeyRunner.sleep(1)

# drag the unlock-ring (from bottom to center (HTC One X))
device.drag((354, 1191), (324, 525), 1, 10)

# unlocked!

This example shows that the lock screen is just secured by the HTC unlock-ring, which must simply be dragged from the bottom to the center. No lock pattern or PIN here (which is much easier to solve with monkeyrunner).
So there’s just one small thing to consider: defining the coordinates of your unlock … thingy’s start end end position. Doing so depends on the device and Android version your using. There will be a tutorial here about that very soon.

via

Ich brauche die UNIX Shell auf meinem Windows System. Und es war erstaunlich einfach.

Cygwin heißt die sich praktisch von selbst installierende Software, die das komplette Linux Look & Feel mitbringt. Zusätzlich ermöglicht es der Installer auf einfachsten Wege hunderte Linux Pakete gleich mitzuinstallieren.

Nach der Installation liegt auf dem Desktop (wenn nicht während der Installation deaktiviert) eine Verknüpfung zum Cygwin Terminal, welcher einer Linux Umgebung nachempfunden ist. Von dort kann man wie auch im Linux Terminal Befehle absetzen, Programme starten, ausführen usw.

Es wird auch direkt eine Linux-ähnliche Ordnerstruktur erstellt und eingegebene Pfade entsprachend übersetzt, sodass auch das Dateisystem Linux-like anmutet:

MS-DOS style path detected: p:/Cygwin/home/Hannes/prog.sh
Preferred POSIX equivalent is: /home/Hannes/prog.sh

Die Ausgaben des Cygwin Terminals entsprechend nicht 1:1 den Ausgaben von Linux Distributionen, man sollte den dort entwickelten Code also nicht ohne Weiteres in Linuxumgebungen übernehmen.
Aber für erste Tests (und die 2 Linux Bash Vorlesungen meines Studiums) reicht es.

Ich musste heute via Batch 2 Firefox Versionsnummern vergleichen und die kleinere Version erkennen. Ist auf den ersten Moment nicht so einfach wie es klingt aber machbar.
Die Versionsnummern bei Firefox bestehen aus maximal 3 Zahlen (seit Version 3), getrennt durch 2 Punkte; ggf. weniger.
Firefox 3.6.28
Firefox 15.0

Ein direkter Vergleich mit Batch ist aufgrund der Punkte nicht möglich; einfach nur die Punkte entfernen würde aber false positives ermöglichen:
3.6.28 = 3628
15.0 = 150
3.6.28 >(!) 15.0

Meine Lösung ist nicht sonderlich schön aber sie funktioniert:

@echo off
setlocal

set alt=9.6.28
set neu=10.0

for /f "tokens=1,2,3 delims=. " %%a in ("%alt%") do set alt1=%%a&set alt2=%%b&set alt3=%%c
for /f "tokens=1,2,3 delims=. " %%a in ("%neu%") do set neu1=%%a&set neu2=%%b&set neu3=%%c

set /a encalt=alt1*1000000+alt2*1000+alt3
set /a encneu=neu1*1000000+neu2*1000+neu3

echo %encalt%
echo %encneu%

echo %encalt% kleiner als %encneu% ?
if %encalt% LSS %encneu% echo JA

endlocal

Es werden Versionsnummern mit bis zu 3 Zahlen verarbeitet; weniger Zahlen geht immer, für mehr Zahlen einfach nur den for Befehl erweitern. Für jede Version wird ein numerischer Wert errechnet und dieser dann verglichen.

Hier ein Test, bei dem ich erst 9.6.28 mit 10.0 vergleiche und dann 9.999.999 mit 10.0. In beiden Fällen wir korrekt ausgegeben, dass 10.0 größer ist. Für größere Versionsnummer in den einzelnen Parts die Multiplikation anpassen.

Fragen, Anregungen, Erweiterungen; Kommentare sind willkommen 🙂