Freitag, 22. November 2013

Nagios: Überwachung eines ESX Clusters und Visualisierung

Wer einen ESX-Cluster im Hause hat, möchte sicherlich auch informiert werden, wenn dort etwas schief geht.

Dazu setze ich das Plugin check_esx3.pl von OP5 ein, dieses liefert, via Webschnittstelle, den Status für ein Datacenter, einzelne ESXi-Hosts oder virtuelle Maschinen.

Als Abhängigkeiten hat es folgende Perl Klassen: Nagios::Plugins, File::Basename und VMware::VIRuntime (muss separat über das Perl SDK von VMWare installiert werden: https://my.vmware.com/de/web/vmware/details?productId=285&downloadGroup=VSP510-SDKPERL-510 )

Überblick:

Usage: check_esx3.pl -D <data_center> | -H <host_name> [ -N <vm_name> ]

    -u <user> -p <pass> | -f <authfile>

    -l <command> [ -s <subcommand> ]

    [ -x <black_list> ] [ -o <additional_options> ]

    [ -t <timeout> ] [ -w <warn_range> ] [ -c <crit_range> ]

    [ -V ] [ -h ]



 -?, --usage

   Print usage information

 -h, --help

   Print detailed help screen

 -V, --version

   Print version information

 --extra-opts=[section][@file]

   Read options from an ini file. See http://nagiosplugins.org/extra-opts

   for usage and examples.

 -H, --host=<hostname>

   ESX or ESXi hostname.

 -C, --cluster=<clustername>

   ESX or ESXi clustername.

 -D, --datacenter=<DCname>

   Datacenter hostname.

 -N, --name=<vmname>

   Virtual machine name.

 -u, --username=<username>

   Username to connect with.

 -p, --password=<password>

   Password to use with the username.

 -f, --authfile=<path>

   Authentication file with login and password. File syntax :

   username=<login>

   password=<password>

 -w, --warning=THRESHOLD

   Warning threshold. See

   http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT

   for the threshold format.

 -c, --critical=THRESHOLD

   Critical threshold. See

   http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT

   for the threshold format.

 -l, --command=COMMAND

   Specify command type (CPU, MEM, NET, IO, VMFS, RUNTIME, ...)

 -s, --subcommand=SUBCOMMAND

   Specify subcommand

 -S, --sessionfile=SESSIONFILE

   Specify a filename to store sessions for faster authentication

 -x, --exclude=<black_list>

   Specify black list

 -o, --options=<additional_options>

   Specify additional command options

 -t, --timeout=INTEGER

   Seconds before plugin times out (default: 30)

 -v, --verbose

   Show details for command-line debugging (can repeat up to 3 times)

Supported commands(^ means blank or not specified parameter) :

    Common options for VM, Host and DC :

        * cpu - shows cpu info

            + usage - CPU usage in percentage

            + usagemhz - CPU usage in MHz

            ^ all cpu info

        * mem - shows mem info

            + usage - mem usage in percentage

            + usagemb - mem usage in MB

            + swap - swap mem usage in MB

            + overhead - additional mem used by VM Server in MB

            + overall - overall mem used by VM Server in MB

            + memctl - mem used by VM memory control driver(vmmemctl) that controls ballooning

            ^ all mem info

        * net - shows net info

            + usage - overall network usage in KBps(Kilobytes per Second)

            + receive - receive in KBps(Kilobytes per Second)

            + send - send in KBps(Kilobytes per Second)

            ^ all net info

        * io - shows disk io info

            + read - read latency in ms (totalReadLatency.average)

            + write - write latency in ms (totalWriteLatency.average)

            ^ all disk io info

        * runtime - shows runtime info

            + status - overall host status (gray/green/red/yellow)

            + issues - all issues for the host

            ^ all runtime info

    VM specific :

        * cpu - shows cpu info

            + wait - CPU wait time in ms

            + ready - CPU ready time in ms

        * mem - shows mem info

            + swapin - swapin mem usage in MB

            + swapout - swapout mem usage in MB

            + active - active mem usage in MB

        * io - shows disk I/O info

            + usage - overall disk usage in MB/s

        * runtime - shows runtime info

            + con - connection state

            + cpu - allocated CPU in MHz

            + mem - allocated mem in MB

            + state - virtual machine state (UP, DOWN, SUSPENDED)

            + consoleconnections - console connections to VM

            + guest - guest OS status, needs VMware Tools

            + tools - VMWare Tools status

    Host specific :

        * net - shows net info

            + nic - makes sure all active NICs are plugged in

        * io - shows disk io info

            + aborted - aborted commands count

            + resets - bus resets count

            + kernel - kernel latency in ms

            + device - device latency in ms

            + queue - queue latency in ms

        * vmfs - shows Datastore info

            + (name) - free space info for datastore with name (name)

            ^ all datastore info

        * runtime - shows runtime info

            + con - connection state

            + health - checks cpu/storage/memory/sensor status

            + maintenance - shows whether host is in maintenance mode

            + list(vm) - list of VMWare machines and their statuses

        * service - shows Host service info

            + (names) - check the state of one or several services specified by (names), syntax for (names):<service1>,<service2>,...,<serviceN>

            ^ show all services

        * storage - shows Host storage info

            + adapter - list bus adapters

            + lun - list SCSI logical units

            + path - list logical unit paths

    DC specific :

        * io - shows disk io info

            + aborted - aborted commands count

            + resets - bus resets count

            + kernel - kernel latency in ms

            + device - device latency in ms

            + queue - queue latency in ms

        * vmfs - shows Datastore info

            + (name) - free space info for datastore with name (name)

            ^ all datastore info

        * runtime - shows runtime info

            + list(vm) - list of VMWare machines and their statuses

            + listhost - list of VMWare esx host servers and their statuses

            + tools - VMWare Tools status

        * recommendations - shows recommendations for cluster

            + (name) - recommendations for cluster with name (name)

            ^ all clusters recommendations


Wie man sehen kann, lassen sich dort sehr viele Dinge überwachen, meiner Meinung nach sind Sachen wie die Auslastung (CPU, RAM, VMFS, IO, Netzwerk) und der Zustand von wichtigen VMs interessante Faktoren, die auf Probleme (Ausfälle, geringe Leistungskapazitäten, ...) hindeuten können.

Dazu hier eine typische commands.cfg:
 
define command {
        command_name    check_esxi_vm_list
        command_line    $USER1$/check_esx3.pl -D $ARG1$ -H $_HOSTESX_HOSTNAME$.DOMAIN.TLD -f $ARG2$ -l runtime -s list
}

define command {
        command_name    check_esxi_status
        command_line    $USER1$/check_esx3.pl -D $ARG1$ -H $_HOSTESX_HOSTNAME$.DOMAIN.TLD -f $ARG2$ -l runtime -s status
}

define command {
        command_name    check_esxi_io
        command_line    $USER1$/check_esx3.pl -D $ARG1$ -H $_HOSTESX_HOSTNAME$.DOMAIN.TLD -f $ARG2$ -l io
}

define command {
        command_name    check_esxi_vmfs
        command_line    $USER1$/check_esx3.pl -D $ARG1$ -H $_HOSTESX_HOSTNAME$.DOMAIN.TLD -f $ARG2$ -l vmfs -w 10%: -c 5%:
}

define command {
        command_name    check_esxi_cpu
        command_line    $USER1$/check_esx3.pl -D $ARG1$ -H $_HOSTESX_HOSTNAME$.DOMAIN.TLD -f $ARG2$ -l cpu -w 80 -c 90
}

define command {
        command_name    check_esxi_mem
        command_line    $USER1$/check_esx3.pl -D $ARG1$ -H $_HOSTESX_HOSTNAME$.DOMAIN.TLD -f $ARG2$ -l mem -w 80 -c 90
}

define command {
        command_name    check_esxi_net
        command_line    $USER1$/check_esx3.pl -D $ARG1$ -H $_HOSTESX_HOSTNAME$.DOMAIN.TLD -f $ARG2$ -l net
}

define command {
        command_name    check_esxi_vm_running
        command_line    $USER1$/check_esx3.pl -D $ARG1$ -H $_HOSTESX_HOSTNAME$.DOMAIN.TLD -f $ARG2$ -l runtime
}

define command {
        command_name    check_esxi_dc_running_hosts
        command_line    $USER1$/check_esx3.pl -D $ARG1$ -f $ARG2$ -l runtime -s listhost -c 8
}
define command {
        command_name    check_esxi_dc_vmfs
        command_line    $USER1$/check_esx3.pl -D $ARG1$ -f $ARG2$ -l vmfs -w 10%: -c 5%:
}

define command {
        command_name    check_esxi_dc
        command_line    $USER1$/check_esx3.pl -D $ARG1$ -f $ARG2$ -l runtime
}
define command {
        command_name    check_esxi_dc_net
        command_line    $USER1$/check_esx3.pl -D $ARG1$ -f $ARG2$ -l net
}

define command {
        command_name    check_esxi_dc_io
        command_line    $USER1$/check_esx3.pl -D $ARG1$ -f $ARG2$ -l io
}
define command {
        command_name    check_esxi_dc_cpu
        command_line    $USER1$/check_esx3.pl -D $ARG1$ -f $ARG2$ -l cpu
}

define command {
        command_name    check_esxi_dc_mem
        command_line    $USER1$/check_esx3.pl -D $ARG1$ -f $ARG2$ -l mem
}

In der commands.cfg verwende ich Custom Hostvariablen, da Hosts im Nagios normalerweise über die IP angelegt werden (keine DNS Abhängigkeit..) und die IPs dann natürlich nicht im Virtual Center matchen würden, diese Host Variablen kann man ganz einfach anlegen:
        _ESX_HOSTNAME                   esx2

Argument 1 muss die IP oder den Hostname des Virtual Centers enthalten, welcher die Abfragen für ein DataCenter bündeln kann.
Argument 2 wäre ein Authentifizierungsfile im Format, dabei ist Domain nur nötig, wenn man AD Authentifizierung nutzt:
username=foobar@domain.tld 

password=barfoo


Wenn die commands.cfg nun eingebunden ist, bietet es sich an eine Hostgruppe für die Veerbung der Service Checks einzurichten (Redundanzen vermeiden!) und dieser dann die ESXi Knoten zuweisen.


Nachdem nun die Service Checks eingerichtet wurden, kann die Überwachung beginnen.

Nun fehlt aber noch das i-Tüpfelchen: die Visualisierung der Auslastung über alle Hosts!

Dazu bediene ich mich dem großartigen PNP4Nagios (Achtung, bitte Version >= 0.6.x nutzen!), die Installation ist, durch die sehr gute Dokumentation, relativ einfach und schnell vollzogen.
Zum Start gibt es bereits ein Template, welches von OP5 im PNP4Nagios mitgeliefert wird.

Dieses unterstützt allerdings keine Multigraphen über mehrere Hosts, daher habe ich dafür eines selbst geschrieben, welches dann so aussieht:



Der grüne Balken ist ein Ticker und zeigt dort direkt Grün, Gelb, Rot an, wenn einer der Hosts ein Problem hat, also die Thresholds überschritten wurden.

Das Template bekommt ihr hier und muss in den templates.special Ordner (z.B: /usr/share/pnp4nagios/templates.special) abgelegt und geringfügig angepasst werden:

Hostnames unterstützen RegEx (z.B. 'esx' => esx1,esx2,...,esxN) oder arrays, falls die Namen kein gleiches Muster haben (array("host1","clusternode2","clusterhost1").
Die ServiceNames (in meinem Fall: ESXi_XXX) müssen genau passen, damit das Special Template funktionieren kann.

Bei Unsicherheiten kann man aber auch einfach die Zeilen "throw new Kohana_exception(print_r($data,TRUE));" auskommentieren und das Template über /pnp4nagios/special anschließend aufrufen, damit werden alle verfügbaren Daten ausgegeben.

$services = $this->tplGetServices("HOST_NAMES",  "SERVICE_NAME")
In Zeile: 12 (CPU), 37 (RAM), 60 (Network)


Wenn nun das Template in dem entsprechenden Ordner abgelegt ist, wird beim Aufruf des PNP4Nagios ein neuer "S" Button auf der rechten Seiten erscheinen, wenn man den auswählt, sollte das neue Template aufgelistet bzw. schon geöffnet sein.

Falls nicht, bitte die Pfade, Berechtigungen und Endung (.php!) überprüfen.


Et voila, die Überwachung der ESXi-Hosts / DataCenter ist eingerichtet und wird in Farbe und bunt angezeigt.


Montag, 28. Oktober 2013

Windows Print Server und die Druckerwarteschlange

Ein typisches Problem in der Windows Printserver Welt scheinen fehlerhafte (=hängende) Druckeraufträge zu sein.
Wenn ein Druckauftrag fehlerhaft ist, blockiert er diesen Drucker, bis dieser Druckauftrag entfernt wurde.

Damit blockierte Drucker nicht durch die BenutzerInnen gemeldet werden müssen (immer unschön und wenig professionell), habe ich einen Check in VBSkript für Nagios/NSclient geschrieben, welcher alle registrierten Drucker mittels WMI auf eben diese Aufträge prüft:

' MSDN: http://msdn.microsoft.com/en-us/library/aa394370%28v=vs.85%29.aspx
' Required Variables
' Author: Oliver Skibbe oliskibbe (at) gmail.com
' Date: 2013-10-30
Const PROGNAME = "check_print_spooler"
Const VERSION = "1.1.0"

' Nagios helper functions
Include "C:\Programme\NSClient++\scripts\lib\NagiosPlugins.vbs"

' Arguments
strComputer = WScript.Arguments.Item(0)

' Defaults
return_code = 0
return_msg = "Everythings fine"

' automatically kill job?
killjob = true


' Create WMI object
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

' Create the NagiosPlugin object
Set np = New NagiosPlugin

' Fetch all jobs with status error
Set Result = objWMIService.ExecQuery("Select * From Win32_PrintJob Where Status = 'Error'")

For Each instance In Result

 ' automatic job kill
 If killjob = True Then
  instance.Delete_
 Else
  ' JobId is attached to "caption, description and name" thus we want to split and accessible with printerName(1)
  printerName = Split(instance.Caption,",")
  ' if job should not be automatically killed, print critical and printer name
  failedPrinterStr = failedPrinterStr & " " & Chr(34) & printerName(0) & Chr(34)
  return_code = 2
 End If
next

If return_code > 0 Then
 return_msg = "Job Errors on printer " & failedPrinterStr
End If

' Nice Exit with msg and exitcode
np.nagios_exit return_msg, return_code


Sub Include( cNameScript )
    Set oFS = CreateObject("Scripting.FileSystemObject")  
    Set oFile = oFS.OpenTextFile( cNameScript )
    ExecuteGlobal oFile.ReadAll()
    oFile.Close
End Sub

Dieses Skript sollte über einen Windows-Server z.B. über NRPE (mit NSCLIENT++) aufgerufen werden und nimmt als Argument den Computernamen an.

Dazu nimmt man folgende Einstellungen im NSClient vor (gilt für Version 0.38):
; Script to check external scripts and/or internal aliases.
CheckExternalScripts.dll
 
[NRPE]
;# NRPE PORT NUMBER
;  This is the port the NRPEListener.dll will listen to.
port=5666

;# COMMAND TIMEOUT
;  This specifies the maximum number of seconds that the NRPE daemon will allow plug-ins to finish executing before killing them off.
command_timeout=60

;# COMMAND ARGUMENT PROCESSING
;  This option determines whether or not the NRPE daemon will allow clients to specify arguments to commands that are executed.
allow_arguments=1

[External Scripts]
check_print_spooler=cscript.exe //T:30 //NoLogo scripts\check_print_spooler.vbs $ARG1$

Der aufmerksame Leser wird festgestellt haben, dass dem Spooler ein Argument übergeben werden kann, dieses nimmt den Namen des Servers an, das heißt man kann von einem Server aus viele andere Server angesprochen werden können, dazu muss dann allerdings der NSClient++ Dienst mit einem entsprechenden User gestartet werden, für den rein lokalen Aufruf ("." oder "localhost") reicht das Systemkonto.

Im Nagios muss dann folgendes in die commands.cfg eingetragen werden:

Entweder die Verteilung des Service über einen Host oder eine Hostgruppe (Argument wäre der "Hop"-Server):
define command {
        command_name    check_windows_print_spooler
        command_line    $USER1$/check_nrpe -H $ARG1$ -c check_print_spooler -a $HOSTADDRESS$
}

Oder als rein lokale Variante:
define command {
        command_name    check_windows_print_spooler
        command_line    $USER1$/check_nrpe -H $HOSTADDRESS$ -c check_print_spooler -a "."
}


Folgende Dinge werde ich wohl noch implementieren:
  • Flag für automatisches Löschen der Jobs
  • E-Mail an Druckjobersteller, dass der Job abgebrochen wurde
  • Umbau auf PHP mit Nutzung von "wmic", d.h. Hop-Server wird überflüssig 

* UPDATE:  Damit das Skript besser skaliert, habe ich den Code etwas umgebaut und ein Flag eingebaut, welches den fehlerhaften Job automatisch löscht.

Download: https://dl.dropboxusercontent.com/u/9482545/check_print_spooler.vbs

Nachtrag zum automatischen Löschen via DBAN

Nachdem nun endlich der Löschserver zum (sehr erfolgreichen) produktiven Einsatz gekommen ist, habe ich doch einen kleinen Fallstrick entdeckt..

Was passiert wenn ich viele Geräte auf einmal einschalte? Richtig, die Sicherung kann sich verabschieden, da beim Starten der PCs erhöhte Last auf der Stromleitung stattfindet...

Fazit: entweder die PCs auf verschiedene Stromkreise aufteilen oder Schritt für Schritt die PCs starten



Backlink: http://oskibbe.blogspot.de/2013/07/wie-loscht-man-sicher-effizient-und.html

 

Passive Sicherheit im AD mit PHP

Ab und an kommt es mal vor, dass ein User im AD erhöhte Rechte oder zusätzliche Gruppen bekommen muss.
Zum Beispiel könnte dieser erhöhte Rechte im Internet (AD-basierte Proxygruppen) oder für eine Applikation (Argh!) benötigen.

Was passiert aber nun, wenn diese Rechte in Vergessenheit geraten und dadurch Schaden (z.B. Data-Loss, Malware, ...) entsteht? Die Frage ist natürlich reichlich polemisch, allerdings muss man sich die Frage stellen, wer dafür die Verantwortung trägt? Natürlich die IT.

Und genau die kann dafür sorgen, dass solche Dinge NICHT in Vergessenheit geraten, indem man sich automatisch in periodischen Abständen ein Aufstellung der wichtigsten Gruppen zuschicken lässt.

Genau dieses erfüllt das Skript, welches ich, aus den oben erwähnten Gefahren heraus, geschrieben habe:


#!/usr/bin/php -q
<?php
/*
 Read dom admins from AD
 Author: Oliver Skibbe oliskibbe (at) gmail.com
 Date: 2013-10-28
*/

function parseLdapDn($dn)
{
        $dn = addcslashes( $dn, "<>" );
        $result = ldap_explode_dn( $dn, 0 );
        //translate hex code into ascii again
        foreach( $result as $key => $value )
                $result[$key] = preg_replace("/\\\([0-9A-Fa-f]{2})/e", "''.chr(hexdec('\\1')).''", $value);
        unset($result["count"]);
        return $result;
}

function sendMail ( $bodyStuff ) {

        global $debug, $dryrun, $to, $replyto, $cc, $from;

        // mail header
        $header = "MIME-Version: 1.0\r\n";
        $header .= "Content-type: text/html; charset=utf-8\r\n";
        $header .= "From: " . $from . "\r\n";
        $header .= "Reply-To: " . $replyto . "\r\n";
  if ( $cc ) {
   $header .= "Cc: " . $cc . "\r\n";
  }
        $header .= "X-Mailer: PHP ". phpversion();
  
        $subject = "Mitglieder der Gruppe {$bodyStuff["name"]}";
        // erfüllt seinen Zweck..
  $body = "<html>
<br>
Mitglieder der Gruppe {$bodyStuff["name"]}
<br><br>
<table border=\"1\">
<tr><th align=\"left\"><b>Username</b></th><th align=\"left\"><b>OU</b></th></tr>\n\n";

        unset($bodyStuff["name"]);

        foreach ( $bodyStuff as $members) {
                foreach ( $members as $member => $foo ) {
                        $body .= "<tr><td nowrap>{$member}</td><td>{$foo}</td></tr>\n";
                }
        }
        $body .= "\n</table>
</html>";
        if ( $debug  || $dryrun ) {
                echo "Subject " . $subject . PHP_EOL;
                echo "Body: " . $body . PHP_EOL;
                echo "Header: " . $header . PHP_EOL;
        }
        if ( ! $dryrun ) {
                $result = mail( $to, $subject, $body, $header);
        } else {
                $result = true;
        }

        return $result;
}

// Variables

// debug prints
$debug = false;

// do not send mail, just print
$dryrun = false;

// receiver
$to = "oliskibbe@gmail.com";
$replyto = "ticketsystem@example.tld";
$from = "absender@example.tld";

// optional
$cc = "chef@example.tld";

// ad
$host = "ADHOST";
$user = "ADUSER";
$pass = "ADPASS";

// erlaube mehrere Gruppen z.B. Domänen-Admin und Proxy Gruppen ohne Beschränkung
$filter = "(&(|(name=Domänen-Admins)(name=FreiesInternet))(objectcategory=group))";
$dn = "dc=example,dc=local";
// nur Name und Mitglieder
$justthese = array('name', 'member');

// mit AD verbinden
$link = ldap_connect($host) or die ("Keine Verbindung zu {$host} möglich!" . PHP_EOL);

// AD Protokoll Einstellungen setzen
ldap_set_option($link, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($link, LDAP_OPT_REFERRALS, 0);

// Am AD anmelden
$bind = ldap_bind($link, $user, $pass) or die ("Keine Anmeldung am AD mit User {$user} an Host {$host} möglich!");

$searchresult = ldap_search ($link, $dn, $filter, $justthese );

$result = ldap_get_entries($link, $searchresult);

ldap_close($link);

// helper

foreach ( $result as $groupArray ) {
        $members = array();

        if (is_array ($groupArray) ) {

                $members["name"] = $groupArray["name"][0];

                // remove count stuff
                unset($groupArray["member"]["count"]);

                foreach ( $groupArray["member"] as $member ) {
                        $dnResult = parseLdapDn($member);

                        $name = str_replace("CN=","",$dnResult[0]);
                        unset($dnResult[0]);

                        $members[$members["name"]][$name] = implode(",", $dnResult);

                        unset($member);
                        unset($dnResult);
                }
                $result = sendMail($members);
                if ( ! $result && $debug ) {
                        echo "Sending mail failed!";
                }
        }
}

Dieses Skript fragt 1-n Gruppen über den LDAP Filter ab und verschickt wunderschöne E-Mails  mit einer tabellarischen Aufstellung der entsprechenden User und in welcher OU sich diese befinden.

Download

Anschließend trägt man dieses Skript in die crontab ein:
0 01 * * 1     /usr/bin/php -q /usr/local/bin/check_ad_admins.php

Nun wird man jede Woche am Montag zum Arbeitsbeginn eine E-Mail mit den aktuellen Gruppenmitgliedern haben, bei sehr großen Firmen sollte man vielleicht noch einen Zwischenschritt einlegen und entweder eine kleine Datenbank oder ein Textfile mit Altwerten füllen um die neu hinzugekommen zu highlighten, aber in meinem Fall war das nicht nötig.


Bei Fragen wie immer einfach melden.

Mittwoch, 25. September 2013

Migration von Benutzerdaten zur Umstellung von Windows XP zu Windows 7 2/2

Die Migration der Benutzerdaten nach einer Umstellung von Windows XP auf Windows 7 ist sehr zeitaufwendig, wie ich meinem vorherigen Blog-Post schon beschrieben habe.

Da man in der IT, Gott sei Dank, größtenteils automatisieren kann, habe ich mir diesen Umstand zur Nutze gemacht und passend zum Sicherungs-Skript, natürlich auch ein Wiederherstellungs-Skript gebaut.

Features:
  • Außenstandorte werden unterstützt => wenn ein bestimmtes Default Gateway gesetzt ist, wird keine Wiederherstellung durchgeführt
  • Konfigurierbares Home-Laufwerk
  • Wiederherstellung nur, wenn Betriebssystem Windows7
  • Wiederherstellung nur, wenn Computername ein bestimmtes Muster hat (z.B: INVW700001)
  • Wiederherstellung von Ordnern
  • Vorbereitung für Start/Stop von Prozessen
  • Error-Handling
  • Migration von OpenOffice zu LibreOffice
  • Warten auf etwaige, laufende Softwarverteilung (Frontrange DSM: Niinst32.exe)
  • Nach Wiederherstellung automatische Abmeldung 
  • Flag bei erfolgte Wiederherstellung (in %APPDATA%)

Hier der Code:

'#####################################################

' Wiederherstellungsscript
'  Author: Oliver Skibbe oliskibbe (at) gmail.com
' Date: 2013-09-25

'#####################################################

' Constants
' Windows Version
Const Win2k = "5.0"
Const WinXP = "5.1"
Const Win2k3 = "5.2"
Const WinVista = "6.0"
Const Win7 = "6.1"
Const Win2k8 = "6.2"

' Stuff
Const Target = "INVW7"
Const HomeDrive = "Z:\"

' Objects
Set WshShell = WScript.CreateObject("WScript.Shell")
Set objFSO = CreateObject("Scripting.FileSystemObject") 
Set objWMIService = GetObject("winmgmts:\\localhost\root\cimv2")

TargetVersion = Win7

' Get Windows Version
Set colOperatingSystem = objWMIService.ExecQuery("Select Version from Win32_OperatingSystem")
For Each objOperatingSystem in colOperatingSystem
 Version = objOperatingSystem.Version
Next

' Quit if target pc name is not XXX or uses other Version than Windows 7
strComputerName = WshShell.ExpandEnvironmentStrings("%COMPUTERNAME%")
If InStr(1, strComputerName, Target, VbTextCompare) = 0 Then
 WScript.Quit
Else
 ' If computer name is valid, check OS version
 If Not Mid(Version,1,3) = TargetVersion Then
  WScript.Quit
 End If 
End If ' end check valid target pc


' paths
strProfileDir = WshShell.ExpandEnvironmentStrings("%USERPROFILE%")
strProgramFilesDir = WshShell.ExpandEnvironmentStrings("%PROGRAMFILES%")
strAppDataDir = strProfileDir + "\AppData"
strLocalAppDataDir = strAppDataDir + "\Local"
strRoamingAppDataDir = strAppDataDir + "\Roaming"

' quit if restore already completed
RestoreFlag = strAppDataDir + "\Restore_done.flag"
If objFSO.FileExists(RestoreFlag) Then
 WScript.Quit
End If

' Get Default GW
Set colNetworkConfiguration = objWMIService.ExecQuery("Select DefaultIPGateway from  Win32_NetworkAdapterConfiguration Where IPEnabled = TRUE")
For Each objNetworkConfiguration in colNetworkConfiguration
 If Not IsNull(objNetworkConfiguration.DefaultIPGateway) Then 
  DefaultGateway = Join(objNetworkConfiguration.DefaultIPGateway, ",")
 End If
Next

' no restore in hannover so far
If DefaultGateway = "10.10.1.1" Then
 Wscript.Quit
End If

' sleep if dsm net install is currently running
ProcToWatch = "NIInst32.exe"
Set colProcesses = objWMIService.ExecQuery("Select * from Win32_Process Where Name ='" & ProcToWatch & "'")
Do Until colProcesses.Count = 0
 WScript.Sleep 10000
 Set colProcesses = objWMIService.ExecQuery("Select * from Win32_Process Where Name ='" & ProcToWatch & "'")
Loop

' Quit if home drive is not available / writable or wrong type
CheckDrive(HomeDrive)

WshShell.Popup "Die Rück-Sicherung der eigenen Dateien wurde gestartet", 3

' Source
SourceBase = HomeDrive + "\Sicherung"
If Not objFSO.FolderExists(SourceBase) Then
 MsgBox("Es wurde keine sicherung in " + SourceBase + " gefunden!")
 WScript.Quit
End If

' Targets
TargetDesktop = strProfileDir & "\Desktop"
TargetOwnFiles = strProfileDir + "\Documents"
TargetFavorites = strProfileDir & "\Favorites"
TargetLOfficeBase = strLocalAppDataDir  + "\LibreOffice\4\user"
TargetRoamingMSOffice = strRoamingAppDataDir + "\Microsoft"
TargetLocalMSOffice = strLocalAppDataDir + "\Microsoft"

' Restore Desktop
Restore SourceBase + "\Desktop", TargetDesktop, "Desktop", "dir", False
' Restore own files
Restore SourceBase + "\Eigene Dateien", TargetOwnFiles, "Eigene Dateien", "dir", False
' Restore Favorites
Restore SourceBase + "\Favoriten", TargetFavorites, "Favoriten", "dir", False
' Restore / Migrate OpenOffice to LibreOffice
Restore SourceBase + "\OpenOffice", TargetLOfficeBase, "LibreOffice", "dir", False
' Restore AppData Local MS Office
Restore SourceBase + "\Local\MSOffice", TargetLocalMSOffice, "MS Office", "dir", False
' Restore AppData Roaming MS Office
Restore SourceBase + "\Roaming\MSOffice", TargetRoamingMSOffice, "MS Office", "dir", False

' touch restore flag
objFSO.CreateTextFile(RestoreFlag)

WshShell.Popup "Die Rück-Sicherung der eigenen Dateien wurde beendet"
' log off
WScript.Sleep 5000
WshShell.run "shutdown /l /t 0"
' End of Main

''' Functions
' control processes
Function ProcessControl(proc, state, CheckState)

 If state = "stop" Then ' stop process
  cmd = "taskkill.exe /F /IM " + proc
 Else ' start process  
  cmd = proc
 End If ' end start / stop state
 
 ExitCode = WshShell.run (cmd, 1, true)
 If CheckState = True Then
  If ExitCode > 0 Then
   MsgBox "Fehler beim " + state + " von " + proc, vbCritical
   WScript.Quit
  End If ' end exit code
 End If ' end check state
End Function

Function Restore(Source, Target, Label, PathType, Required)
  ' try to copy folder
  If PathType = "dir" Then
  
   If objFSO.FolderExists(Source) Then
    If Not objFSO.FolderExists(Target) Then
     CreateFolderRecursive Target
    End If
    objFSO.CopyFolder Source, Target
   Else
    ' If required but does not exist: Quit!
    If Required = True Then
     MsgBox("Fehler bei der Rücksicherung von " + Label)
     WScript.Quit
    End If ' End Required Output
   End If ' end check source and target folder
  ' try to copy file 
  Elseif PathType = "file" Then
   ' try to copy file, source file name and target directory  
   If objFSO.FileExists(Source) And objFSO.FolderExists(Target) Then
    objFSO.CopyFile Source, Target
   Else
    ' If required but does not exist: Quit!
    If Required = True Then
     MsgBox("Fehler bei der Rücksicherung von " + Label)
     WScript.Quit
    End If ' End Required Output
   End If ' end check source and target file   
  ' not supported 
  Else  
   MsgBox("Typ wird nicht unterstützt, wählen Sie: dir, file")
   WScript.Quit
  End If
End Function

' Check target drive
Function CheckDrive(Drive)
  
 If objFSO.DriveExists(Drive) Then
  Set DriveState = objFSO.GetDrive(Drive)  
  ' Check home drive 
  If Not DriveState.IsReady = True Then
   ErrorText = "Laufwerk " + Drive + " ist nicht erreichbar, bitte starten Sie den PC neu!"
   ErrorOccured = True
  Else 
   ' 0: unkown, 1: Removable, 2: Fixed, 3: Network, 4: CD-Rom, 5: RAM-Disk
   If Not DriveState.DriveType = 1 And Not DriveState.DriveType = 2 And Not DriveState.DriveType = 3 Then 
    ErrorText = Drive + ": ist kein gültiger Laufwerkstyp, mögliche Typen: Netzwerk, Festplatte, Wechseldatenträger"
    ErrorOccured = True
   End If ' end check valid drive type
  End If
 Else
  ErrorText = "Laufwerk " + Drive + " existiert nicht, bitte starten Sie den PC neu!"
  ErrorOccured = True
 End If ' End Drive exists
 
 If ErrorOccured = True Then
  MsgBox(ErrorText)
  WScript.Quit
 End If
End Function

Function CreateFolderRecursive(FullPath)
 arr = split(FullPath, "\")
 path = ""
 For Each dir In arr
  If path <> "" Then 
   path = path & "\"
  End If
  path = path & dir
  If Not objFSO.FolderExists(path) Then 
   objFSO.CreateFolder(path)
  End If
 Next
End Function
' EOF


Das Skript sollte angepasst, getestet und im NETLOGON Verzeichnis abgelegt werden, anschließend kann es im Login-Skript verankert werden und stellt ab diesem Zeitpunkt automatisch bei der ersten Anmeldung die Daten wieder her.

Zum Download

Bei Fragen bitte melden!

Dienstag, 17. September 2013

Migration von Benutzerdaten zur Umstellung von Windows XP zu Windows 7 oder wie sichere ich automatisch für BenutzerInnen lokale Daten 1/2

In einem modernen Unternehmen werden häufig serverbasierte Profile und/oder Ordnerumleitungen für die persönlichen, PC-basierten, Daten der BenutzerInnen eingesetzt.

Dieses hat den Vorteil, dass auf den lokalen PCs keine Daten vorhanden sind und somit die Gefahr des Datenverlustes bei Ausfall, Diebstahl, Elementare Gefährdungen, etc. minimiert wird.

Wenn nun allerdings der Umstieg auf Windows 7 ansteht, wird einem bei Tests sehr schnell auffallen, dass die XP Profile nicht mit den W7 Profilen gemischt werden können und man somit, bei Anmeldung an Windows 7, nur ein temporäres Profil zugewiesen bekommt bzw. im schlimmsten Fall das XP Profil zerschiesst.
Dieses Problem tritt auf, da zu einem die Registry (betrifft serverbasierte Profile) andere Einträge aufweist, sowie die Verzeichnisstruktur (z.B. Ordnerumleitungen) seit Windows Vista eine andere ist.

Kurzer Exkurs zu den beiden Möglichkeiten:

Serverbasierte Profile

Das serverbasierte Profil beinhaltet das gesamte Profil eines Benutzer-Accounts, inkl. Registry und %USERPROFILE% (genauere Informationen dazu gibt es bei Microsoft und zwar hier). Die Daten werden auf den lokalen PC geschrieben und gelesen, d.h. es findet eine Synchronisation zwischen PC und Server statt.

Vorteil(e):
  • Alle Daten sind vollständig auf dem Server => Vermeidung von Datenverlusten
  • Alle Daten sind vollständig auf dem Client => Offline Arbeit möglich
Nachteil(e):
  • Verzögerung beim Anmelden, da das gesamte Profil vom Server heruntergeladen, also synchronisiert wird, je nach Bandbreite und Auslastung des Servers kann dieses zu unangenehmen Verzögerungen und eingeschränktem Produktionsausfall führen. Beispiel: die Anmeldung dauert 5 Minuten mit einem serverbasierten Profil und 1 Minute mit lokalem Profil, in einem Arbeitsjahr würde sich das Ganze zu 14,7 Stunden summieren und somit pro BenutzerIn zu fast 2 Tagen Produkionsausfall per anno führen würde
  • Synchronisation der Daten erst bei Abmeldung, bei Ausfall des PCs sind die letzten Änderungen nicht gesichert, Abmeldung wird ebenfalls verzögert
  • Höhere Grundlast auf den Servern/Storage und Netzwerk zu Stoßzeiten (Beginn, Mittagspause, Feierabend)

Ordnerumleitungen

Ordnerumleitungen können über Gruppenrichtlinien konfiguriert werden und bieten eine einfache Möglichkeit zur Umleitung von Ordnern auf andere Systeme, gerne für Ordner wie Eigene Dateien oder Desktop genutzt, damit die Anmeldezeit verringert, aber die Gefährdung durch Datenverlust dennoch minimiert.

Vorteil(e):
  • Konfigurierte sind vollständig auf dem Server => Vermeidung von Datenverlusten
  • Keine Verzögerung beim An- und Abmelden
 Nachteil(e):
  • Höhere Grundlast auf den Servern/Storage und Netzwerk, da Live gearbeitet wird
  • Keine Offline-Arbeit möglich

Da beide Lösungen jeweils Vor- und Nachteile bieten, sollte genau geprüft werden, welche von beiden genutzt oder wie beide kombiniert werden sollten.
Typisch wird das serverbasierte Profil in Kombination mit Ordnerumleitung von Desktop und Eigene Dateien genutzt, so hat man eine effiziente Lösung mit geringem Impact auf die Datensicherheit und Gesamt Performance


Zurück zum Thema, bei der Umstellung von Windows XP auf Windows 7 gibt es natürlich nicht nur die Profile/persönlichen Daten zu beachten, sondern es gilt auch Dinge zu prüfen, wie z.B.:
  • Der wichtigste Punkt: Software-Kompabilität zu Windows 7, inkl. Architektur (Test durch IT, Fachabteilung)
  • Upgrade der Domänen-Controller (Achtung, abhängige Authentifizierungsdienste bedenken, z.B. SSPI, NTLMv1) zur Nutzung der neuen GPO Features
  • Schulung, Schulung und Schulung => erhöht Akzeptanz der Mitarbeiter, selten gilt: Neu ist immer besser ;-)
  • Planung, Planung und Planung: Test, Logistik (Wie, wo, wann und durch wen wird installiert), Rollout (Etagenweise, Abteilungsweise, ...), Transparenz (Was funktioniert wie, Wann passiert was) => Akzeptanz der Mitarbeiter

In meinem Anwendungsfall kommen keine serverbasierten Profile zum Einsatz, das hat zur Folge, das Daten wie z.B. Favoriten, Wörterbücher, Signaturen (falls nicht automatisiert, siehe dazu meine vorherigen Einträge), Dokumente nur lokal auf den PCs abgelegt werden.
Es gibt nun zwei Wege um dieses "Dilema" zu lösen.
Der eine Weg ist eine organisatorische Lösung, d.h. die BenutzerInnen werden informiert, dass ab sofort alle Daten auf dem Home Laufwerk gespeichert bzw. dort hin verschoben werden sollen, der andere Weg ist eine technische Lösung in Form eines Skripts, dass diese Aufgabe selbstständig erledigt.
Um die größtmögliche Akzeptanz mit Effizienz zu kombinieren, habe ich mich für einen gemischten Weg entschieden, d.h. es gibt eine Dienstanweisung, die sagt, dass alles Zukünftige nur noch auf dem Home-Laufwerk gespeichert werden soll (+ Ordnerumleitungen), sowie zwei Skripte zur Sicherung und Wiederherstellung wurden von mir geschrieben.

Da natürlich, aus Gründen des Datenschutz, kein Zugriff auf die Home-Laufwerke der BenutzerInnen besteht, kommt keine serverbasierte Sicherung der Daten in Frage, sondern eine Lösung, die durch den Benutzer entweder manuell oder automatisch gestartet wird.

Entschieden habe ich mich für die automatische Lösung mit Hilfe des Login-Skripts, somit wird bei Login eine Sicherung der konfigurierten Pfade durchgeführt, Nachteil: die Sicherung kann unvollständig sein. Ich habe mich bewusst für das Login-Skript entschieden, da BenutzerInnen beim Herunterfahren des PCs häufig den Bildschirm als Erstes ausschalten und somit Bildschirmmeldungen nicht mehr sehen können (natürlich wäre auch eine automatisierte E-Mail aus dem Skript heraus möglich, allerdings werden diese auch gern ignoriert..)

Folgende Features unterstützt das Sicherungs-Skript (durch Anpassung der Pfade und der Version kann das Skript auch als normales Sicherungsskript bei PC-Wechsel genutzt werden!):
  • Außenstandorte werden unterstützt => wenn ein bestimmtes Default Gateway gesetzt ist, wird keine Sicherung durchgeführt
  • Konfigurierbares Home-Laufwerk
  • Sicherung nur, wenn Betriebssystem Windows XP
  • Sicherung nur, wenn Computername ein bestimmtes Muster hat (z.B: PCXP00001)
  • Meldung bei Überschreitung der konfigurierbaren Größe
  • Rekursive Sicherung von Ordner, bei Ausschluss von Dateien größer als X (z.B. Outlook Ordner ohne pst-file)
  • Meldung, wenn Dateien in einem Ordner gefunden werden, die zu Tag X (Datum/Meldung konfigurierbar, abhängig vom Default Gateway) erledigt sein müssen
  • Start/Stop von Prozessen
  • Error-Handling
  • Default Sicherungspfade: Desktop (250 MB), Eigene Dateien (250 MB), Outlook (pst file bis 100 MB), MS Office, OpenOffice (Textbausteine, Wörterbücher)

Hier der Code
'#####################################################

' Sicherungsscript
'  Author: Oliver Skibbe oliskibbe (at) gmail.com
' Date: 2013-09-17

'#####################################################

' Constants
' Windows Version
Const Win2k = "5.0"
Const WinXP = "5.1"
Const Win2k3 = "5.2"
Const WinVista = "6.0"
Const Win7 = "6.1"
Const Win2k8 = "6.1"

' Stuff
Const Target = "INV"
Const HomeDrive = "Z:\"

' max transferred bytes
' 100 MB
Const maxDefaultSize = 104857600
' 250 MB
Const maxDesktopSize = 262144000
Const maxOwnFilesSize = 262144000

TargetVersion = WinXP

' Objects
Set WshShell = WScript.CreateObject("WScript.Shell")
Set objFSO = CreateObject("Scripting.FileSystemObject") 
Set objWMIService = GetObject("winmgmts:\\localhost\root\cimv2")

' Get Windows Version
Set colOperatingSystem = objWMIService.ExecQuery("Select Version from Win32_OperatingSystem")
For Each objOperatingSystem In colOperatingSystem
 Version = objOperatingSystem.Version
Next

' Get Default GW
Set colNetworkConfiguration = objWMIService.ExecQuery("Select DefaultIPGateway from  Win32_NetworkAdapterConfiguration Where IPEnabled = TRUE")
For Each objNetworkConfiguration in colNetworkConfiguration
 If Not IsNull(objNetworkConfiguration.DefaultIPGateway) Then 
  DefaultGateway = Join(objNetworkConfiguration.DefaultIPGateway, ",")
 End If
Next

' Quit if target pc name is not XXX or uses other Version than Windows 7
strComputerName = WshShell.ExpandEnvironmentStrings("%COMPUTERNAME%")
If InStr(1, strComputerName, Target, VbTextCompare) = 0 Then
 WScript.Quit
Else
 ' If computer name is valid, check OS version
 If Not Mid(Version,1,3) = TargetVersion Then
  WScript.Quit
 End If 
End If ' end check valid target pc

' quit if hannover gw ip => no backup
If DefaultGateway = "10.10.1.1" Then
 Wscript.Quit
End If

' quit if Foobar town gw ip => no backup
If DefaultGateway = "10.16.1.1" Then
 Wscript.Quit
End If

' Quit if home drive is not available / writable or wrong type
CheckDrive(HomeDrive)

' Helper vars
strProgramFiles = WshShell.ExpandEnvironmentStrings("%PROGRAMFILES%")
strProfileDir = WshShell.ExpandEnvironmentStrings("%USERPROFILE%")
strAppDataDir = WshShell.ExpandEnvironmentStrings("%APPDATA%")
strLocalDir = strProfileDir + "\Lokale Einstellungen\Anwendungsdaten"

' Source dirs
SourceDesktop = strProfileDir & "\Desktop"
SourceFavorites = strProfileDir & "\Favoriten"
SourceOwnFiles = strProfileDir + "\Eigene Dateien"
SourceOOfficeBase = strAppDataDir  + "\OpenOffice.org\3\user"
SourceRoamingMSOfficeBase = strAppDataDir  + "\Microsoft"
SourceLocalMSOfficeBase = strLocalDir  + "\Microsoft"

' Target dirs
TargetBase = HomeDrive + "\Sicherung"
TargetDesktop = TargetBase + "\Desktop"
TargetOwnFiles = TargetBase + "\Eigene Dateien"
TargetFavorites = TargetBase + "\Favoriten"
TargetOOffice = TargetBase + "\OpenOffice"
TargetRoamingMSOffice = TargetBase + "\Roaming\MSOffice"
TargetLocalMSOffice = TargetBase + "\Local\MSOffice"

' create backup folder if not exists
If Not objFSO.FolderExists(TargetBase) Then
 objFSO.CreateFolder TargetBase
End If ' End check folder

WshShell.Popup "Die Sicherung der eigenen Dateien wurde gestartet", 2

'''' Desktop ''''
 BackupFolder SourceDesktop, TargetDesktop, "Desktop", maxDesktopSize, False

'''' Own files ''''
 BackupFolder SourceOwnFiles, TargetOwnFiles, "Eigene Dateien", maxOwnFilesSize, False

'''' Favorites ''''
 BackupFolder SourceFavorites, TargetFavorites, "Favoriten", maxDefaultSize, False

'''' OpenOffice ''''
 BackupFolder SourceOOfficeBase + "\autotext", TargetOOffice + "\autotext", "OOffice Textbausteine", maxDefaultSize, False
 BackupFolder SourceOOfficeBase + "\wordbook", TargetOOffice + "\wordbook", "OOffice Wörterbuch", maxDefaultSize, False
 
'''' Microsoft Office ''''
 BackupFolder SourceRoamingMSOfficeBase + "\Templates", TargetRoamingMSOffice + "\Templates", "MS Office Templates", maxDefaultSize, False
 BackupFolder SourceRoamingMSOfficeBase + "\Signatures", TargetRoamingMSOffice + "\Signatures", "MS Office Signatures", maxDefaultSize, False
 BackupFolder SourceRoamingMSOfficeBase + "\Document Building Blocks", TargetRoamingMSOffice + "\Document Building Blocks", "MS Office Textbausteine", maxDefaultSize, False

'''' Outlook folder without pst (via file size) ''''
 BackupFolderRecursiveExcludeMaxSize SourceRoamingMSOfficeBase + "\Outlook", TargetRoamingMSOffice + "\Outlook", "Outlook", maxDefaultSize, False
 BackupFolderRecursiveExcludeMaxSize SourceLocalMSOfficeBase + "\Outlook", TargetLocalMSOffice + "\Outlook", "Outlook", maxDefaultSize, False

'''' Auftragsdaten ''''
' Message if files are available (could be just in addition to a due time)
CheckFileCountInFolder "C:\Auftragsdaten\Daten", "Auftragsdaten", 1



' Target Size Output
Set objFolder = objFSO.GetFolder(TargetBase) 
' returns byte
TargetSize =  objFolder.Size

WshShell.Popup "Die Sicherung der eigenen Dateien wurde beendet. Es wurden " + ConvertSize(TargetSize) + " auf Ihr Laufwerk " + HomeDrive + " übertragen", 3
' End of Main

''' Functions
' control processes
Function ProcessControl(proc, state, CheckState)

 If state = "stop" Then ' stop process
  cmd = "taskkill.exe /F /IM " + proc
 Else ' start process  
  cmd = proc
 End If ' end start / stop state
 
 ExitCode = WshShell.run (cmd, 1, true)
 If CheckState = True Then
  If ExitCode > 0 Then
   MsgBox "Fehler beim " + state + " von " + proc, vbCritical
   WScript.Quit
  End If ' end exit code
 End If ' end check state
End Function

' Pretty output for bytes
Function ConvertSize(Size)
 Do While InStr(Size,",")
  CommaLocate = InStr(Size,",")
  Size = Mid(Size,1,CommaLocate - 1) & _
    Mid(Size,CommaLocate + 1,Len(Size) - CommaLocate)
 Loop

 Suffix = " B"
 If Size >= 1024 Then suffix = " KB"
 If Size >= 1048576 Then suffix = " MB"
 If Size >= 1073741824 Then suffix = " GB"
 If Size >= 1099511627776 Then suffix = " TB"

 Select Case Suffix
  Case " KB" Size = Round(Size / 1024, 1)
  Case " MB" Size = Round(Size / 1048576, 1)
  Case " GB" Size = Round(Size / 1073741824, 1)
  Case " TB" Size = Round(Size / 1099511627776, 1)
 End Select

 ConvertSize = Size & Suffix
End Function

' return due dates e.g. till something will be done, see CheckFileCountInFolder
Function RolloutDate(DefaultGateway)
 ' Default
 Rollout = "Offen"
 
 Set objRolloutDates = CreateObject("Scripting.Dictionary")
 ' Foobar town
 objRolloutDates.Add "10.16", "05.09"
 ' Hannover
 objRolloutDates.Add "10.10", "Offen"

 For Each RolloutDate In objRolloutDates
  If InStr(1, DefaultGateway, RolloutDate, VbTextCompare) > 0 Then
   RolloutDate = objRolloutDates(RolloutDate)
   Exit Function
  End If
 Next
End Function

' Copy Folder
Function BackupFolder(Source, Target, Label, maxSize, Required) 
 ' check if source exists
 If objFSO.FolderExists(Source) Then
  ' create target folder
  If Not objFSO.FolderExists(Target) Then
   CreateFolderRecursive Target
  End If ' End check folder
  
  Set objFolder = objFSO.GetFolder(Source) 
  ' returns byte
  FolderSize =  objFolder.Size
  ' copy if folder size is less than ~XXX Megabyte..
  If FolderSize < maxSize Then
   objFSO.CopyFolder Source, Target
  Else
   MsgBox(Label + " ist zu groß: " + ConvertSize(FolderSize) + ". Bitte löschen Sie unnötige Dateien oder verschieben Sie diese auf Ihr Laufwerk H:\. Falls diese Schritte nicht helfen, wenden Sie sich bitte an die Hotline")
  End If ' end of size
 Else
  If Required = True Then
   MsgBox(Label + " (" + Source + ") wurde nicht gefunden, muss aber vorhanden sein!")
   WScript.Quit
  End If
 End If
End Function

' Copy File
Function BackupFile(Source, Filename, Target, Label, maxSize, Required) 

 FullPath = Source + "\" + Filename
 
 ' check if source exists
 If objFSO.FileExists(FullPath) Then
  ' create target folder
  If Not objFSO.FolderExists(Target) Then
   CreateFolderRecursive Target
  End If ' End check folder
  
  Set objFile = objFSO.GetFile(FullPath) 
  ' returns byte
  FileSize =  objFile.Size
  ' copy if folder size is less than ~XXX Megabyte..
  If FileSize < maxSize Then
   objFSO.CopyFile FullPath, Target + "\" + Filename
  Else
   MsgBox(Label + " ist zu groß: " + ConvertSize(FileSize) + ". Bitte löschen Sie diese Datei oder verschieben Sie diese auf Ihr Laufwerk H:\. Falls diese Schritte nicht helfen, wenden Sie sich bitte an die Hotline")
  End If ' end of size
 Else
  If Required = True Then
   MsgBox(Label + " (" + Source + ") wurde nicht gefunden, muss aber vorhanden sein!")
   WScript.Quit
  End If
 End If
 
End Function

' check if x file(s) exist in given path
Function CheckFileCountInFolder(Path, Label, maxFiles)

 FileCount = 0
 
 If objFSO.FolderExists(Path) Then
  Set objFolder = objFSO.GetFolder(Path)
  Set colFiles = objFolder.Files
  For Each objFile In colFiles
   FileCount = FileCount + 1  
  Next
  If maxFiles > 0 And FileCount >= maxFiles Then
   MsgBox( Cstr(FileCount) + " Datei(en) in " + Label + " vorhanden, bitte bis zum " + Chr(34) + RolloutDate(DefaultGateway) + Chr(34) + " abarbeiten / entfernen oder bei der IT-Hotline melden!")
  End If 
 End If ' end FileExists 
End Function

' Copy Folder
Function BackupFolderRecursiveExcludeMaxSize(Source, Target, Label, maxSize, Required) 
 ' check if source exists
 If objFSO.FolderExists(Source) Then
  ' create target folder
  If Not objFSO.FolderExists(Target) Then
   CreateFolderRecursive Target
  End If ' End check folder
  
  Set objFolder = objFSO.GetFolder(Source) 
  Set Files = objFolder.Files
  For Each File in Files
   If Not InStr(1, File.Name, "outlook.ost", VbTextCompare) > 0 Then
    If File.Size < maxSize Then
     File.Copy(Target + "\" + File.Name)
    Else
     MsgBox(Label + " ist zu groß: " + ConvertSize(File.Size) + ". Bitte löschen Sie diese Datei oder verschieben Sie diese auf Ihr Laufwerk H:\. Falls diese Schritte nicht helfen, wenden Sie sich bitte an die Hotline")
    End If
   End If
  Next 
 Else
  If Required = True Then
   MsgBox(Label + " (" + Source + ") wurde nicht gefunden, muss aber vorhanden sein!")
   WScript.Quit
  End If
 End If
 
End Function

' Check target drive
Function CheckDrive(Drive)
  
 If objFSO.DriveExists(Drive) Then
  Set DriveState = objFSO.GetDrive(Drive)  
  ' Check home drive 
  If Not DriveState.IsReady = True Then
   ErrorText = "Laufwerk " + Drive + " ist nicht erreichbar, bitte starten Sie den PC neu!"
   ErrorOccured = True
  Else 
   ' 0: unkown, 1: Removable, 2: Fixed, 3: Network, 4: CD-Rom, 5: RAM-Disk
   If Not DriveState.DriveType = 1 And Not DriveState.DriveType = 2 And Not DriveState.DriveType = 3 Then 
    ErrorText = Drive + ": ist kein gültiger Laufwerkstyp, mögliche Typen: Netzwerk, Festplatte, Wechseldatenträger"
    ErrorOccured = True
   End If ' end check valid drive type
  End If
 Else
   ErrorText = "Laufwerk " + Drive + " existiert nicht, bitte starten Sie den PC neu!"
   ErrorOccured = True
 End If ' End Drive exists
 
 If ErrorOccured = True Then
  MsgBox(ErrorText)
  WScript.Quit
 End If
End Function

Function CreateFolderRecursive(FullPath)
 Set oFs = WScript.CreateObject("Scripting.FileSystemObject")
 arr = split(FullPath, "\")
 path = ""
 For Each dir In arr
  If path <> "" Then path = path & "\"
  path = path & dir
  If oFs.FolderExists(path) = False Then oFs.CreateFolder(path)
 Next
End Function

' EOF

Das Skript sollte angepasst, getestet und im NETLOGON Verzeichnis abgelegt werden, anschließend kann es im Login-Skript verankert werden und sichert ab diesem Zeitpunkt automatisch bei jeder Anmeldung die Daten.

Zum Download

Bei Fragen bitte melden!