Dienstag, 27. März 2018

PCs Herunterfahren

In meiner Schule gibt es nicht nur Computerräume, in jedem Klassenzimmer stehen PCs, welche immer benutzbar sein müssen. Aber wie werden diese ausgeschaltet?

Um dieses Problem zu lösen habe ich ein Powershell-Skript geschrieben, welche die PCs herunterfahren kann. Das Skript wird auf der AdminVM alle 30 Minuten ausgeführt und fährt PCs herunter, falls dies erforderlich ist.

Da mein Skript nicht ohne weiteres auf alle Schulen übertragbar ist möchte ich hier eine Anleitung zum eigenen Zusammenbau geben.Speichern Sie das Skript z.B. auf dem Homelaufwerk des Administrators in einem Order "Skripte" unter einem Namen Ihrer Wahl ab, z.B. "herunterfahren.ps1".

1. Welche Rechner sollen heruntergefahren werden?

Sie können per Powershell Rechner aus Raumgruppen auslesen.
Um allen Rechnern einer Raumgruppe 'schule-Klassenzimmer' einen Shutdown-Befehl zu schicken müssen Sie die Rechner auslesen. Dies funktioniert mit folgendem Befehl:
$groupname = 'schule-Klassenzimmer'
$a = (New-Object System.DirectoryServices.DirectoryEntry((New-Object System.DirectoryServices.DirectorySearcher("(&(objectCategory=Group)(name=$($groupname)))")).FindOne().GetDirectoryEntry().Path)).member | % { (New-Object System.DirectoryServices.DirectoryEntry("LDAP://"+$_)) } | Sort-Object sAMAccountName | SELECT @{name="User Name";expression={$_.Name}},@{name="User sAMAccountName";expression={$_.sAMAccountName}}

Jedem dieser PCs schicken Sie nun den Stop-Computer Befehl.

foreach($as in $a){     
Stop-Computer -ComputerName $as.'User Name' -force -ErrorAction SilentlyContinue
}
Wenn Sie dieses Skript zur gewünschten Zeit ausführen, werden die PCs des Raumes heruntergefahren.

2. Bedingungen zum Herunterfahren

Sie können das Skript auch alle 30 minuten ausführen lassen. Dann ist es sinnvoll, wenn geprüft wird, ob die Comuter heruntergefahren werden sollen oder nicht. Dafür können Sie eine Variable setzen und abfragen.
$herunterfahren=$false...
...
...
if($herunterfahren){     
$groupname = 'schule-Klassenzimmer'     
$a = (New-Object System.DirectoryServices.DirectoryEntry((New-Object System.DirectoryServices.DirectorySearcher("(&(objectCategory=Group)(name=$($groupname)))")).FindOne().GetDirectoryEntry().Path)).member | % { (New-Object System.DirectoryServices.DirectoryEntry("LDAP://"+$_)) } | Sort-Object sAMAccountName | SELECT @{name="User Name";expression={$_.Name}},@{name="User sAMAccountName";expression={$_.sAMAccountName}}     
foreach($as in $a){           
Stop-Computer -ComputerName $as.'User Name' -force -ErrorAction SilentlyContinue       
}}
Nun müssen Sie noch prüfen, ob das Herunterfahren gesetzt werden muss. Dafür können Sie die Abschaltzeit in eine Variable ablegen.
$abschaltZeit = 17
Nun können Sie Prüfen ob die PCs heruntergefahren werden sollen, wenn z.B. die Aktuelle Stunde größer gleich der abschaltZeit ist.
if($heute.Hour -ge $abschaltZeit){   
$herunterfahren=$true
}
In meiner Schule starten die PCs per Bios Einschaltzeit. D.h. die PCs fahren jeden Werktag um 7.40 hoch. In den Ferien ist dies jedoch ein Problem, die PCs sollen möglichst direkt wieder ausgeschaltet werden. Dazu können Sie Prüfen, ob gerade Schulferien vorliegen. Dazu können Sie den aktuellen Ferienkalender von Schulferien.org ICAL herunterladen.
Laden Sie den Inhalt der Datei in Powershell
$ferien = Get-Content -Path "H:\PFAD\ferien_baden-wuerttemberg_2018.ics"
Nun müssen Sie die Prüfen ob das heutige Datum zwischen einem Ferienbeginn und einem Ferienende liegt.
$heute = get-date
foreach($zeile in $ferien){   
if($zeile.toString().Contains("DTSTART")){         
$anfang =[datetime]::ParseExact($zeile.toString().Substring($zeile.Length-8),'yyyyMMdd',$null)   
}   
if($zeile.toString().Contains("DTEND")){       
$ende =[datetime]::ParseExact($zeile.toString().Substring($zeile.Length-8),'yyyyMMdd',$null)    
if (($heute -ge $anfang) -and ($heute -le $ende)){                 
$herunterfahren = $true    
}}}
Für meine Schule frage ich bei den Ferien noch ab, ob es vor 9Uhr morgens ist. Nur dann setze ich das Herunterfahren aufgrund von Ferien. So stellen Sie sicher, dass von Hand eingeschaltete PCs ab 9Uhr nicht alle 30 Minuten abgeschaltet werden.
 if (($heute -ge $anfang) -and ($heute -le $ende) -and ($heute.Hour -le 9)){
Wenn Sie diesen Teil über der Prüfung if($herunterfahren){ einfügen werden die Computer nur heruntergefahren, wenn Ferien sind oder die Abschaltzeit erreicht wurde.

3. Skript Zeit-gesteuert ausführen. 

Zum zeit-gesteuerten Ausführen öffnen Sie die AdminVM, drücken Sie die Windows-taste und geben Sie "Aufgabenplanung" ein.
Klicken Sie auf "Aufgabe erstellen..." und geben Sie der Aufgabe einen Namen, z.B. "Schulcomputer herunterfahren".
Wählen Sie den Reiter "Trigger" und klicken Sie auf "Neu" und legen Sie hier Ihren Zeiplan an.
Stellen Sie eine Startzeit ein, z.B. 8:00:00 und Täglich. Klicken Sie auf "Wiederholen jede: " und stellen Sie 30 Minuten für die Dauer von 12 Stunden ein.Klicken Sie nun auf OK.
Im Reiter "Aktionen" klicken Sie auf "Neu". Die Aktion "Programm starten" ist voreingestellt. Bei Programm Tragen Sie "Powershell.exe" ein. Bei Parametern Tragen Sie
-ExecutionPolicy Bypass h:\PFAD\meinSkript.ps
ein. Mit einem Klick auf OK sollte das Skript nun täglich alle 30 Minuten ausgeführt werden.
Quelle

4. Erweiterungen

Wenn Sie beim Erstellen des Skripts eine Rückgabe in die Konsole wünschen können Sie
Write-Host "Ein Text" 
Write-Host $eineVariable 
Write-Host "Oder auch " $beides 
ausgeben lassen.

Powershell ist ein sehr mächtiges Werkzeug zur Administration von Computern. Ich habe versucht bei der Beschreibung nur das nötigste einzubauen. In meinem eigenen Skript führe ich noch Logdateien, kann die Namen der Ferien auslesen, Rechner aus mehreren Raumgruppen ansteuern uvm. Diese Erweiterungen führen an dieser Stelle allerdings etwas zu weit. Bei Rückfragen oder Anmerkungen senden Sie mir gerne Feedback.Mein vorläufiges Skript finden Sie unter Punkt 6.

5. Ausblick: Herunterfahren abhängig vom WebUntis Stundenplan. 

Mein letztes Projekt war es, zu Prüfen ob das Klassenzimmer in der WebUntis-Sundenplan-Software belegt ist. Sobald das Zimmer an einem Tag nicht mehr gebucht ist werden die PCs heruntergefahren. Das Problem hierbei ist vor allem, dass die Namen der Computer in den Klassenzimmern nicht zwingend mit den in WebUntis hinterlegten Raumnamen übereinstimmt. Die Umsetzung können Sie im Skript unten sehen, das genaue Vorgehen werde ich in einem extra Blog-Eintrag dokumentieren.

6. Mein Skript (ohne Gewähr, Stand März 2018)

Powershell

$d = Get-Content -Path "H:\Skripte\ferien_baden-wuerttemberg_2018.ics"
$abschaltZeit = 17
$raumGruppen =  'schule-Jahnstr', 'schule-KlassenzimmerBerlin'
$raumGruppenUntis = 'schule-Jahnstr'

$success = $false
$ferien=$false
$spaet = $false
$heute = get-date

                $log = "Das Skript wurde um " +$heute+" ausgeführt."
                Out-File -FilePath 'H:\Skripte\logXtreme.txt' -Append -InputObject $log



#### Ließt die Ferien in der Datei $d aus. Falls das heuteige Datum in den Ferien liegt wird $success zu $true #### 

if($heute.Hour -le 9){
### Vor 9 Uhr wird geprüft, ob ein Ferientag vorliegt und heruntergefahren, ab 9Uhr nichtmehr. ###
foreach($ds in $d){
    
    if($ds.toString().Contains("SUMMARY")){
        $testFerien =$ds.toString().TrimStart("SUMMARY:")
    }
    
    if($ds.toString().Contains("DTSTART")){
        $anfang =[datetime]::ParseExact($ds.toString().Substring($ds.Length-8),'yyyyMMdd',$null)

    }
    if($ds.toString().Contains("DTEND")){
        $ende =[datetime]::ParseExact($ds.toString().Substring($ds.Length-8),'yyyyMMdd',$null)

    if (($heute -ge $anfang) -and ($heute -le $ende))
    {
            $success = $true
            $ferien=$testFerien
    }

    }
}
}

#### Prüft, ob die Uhrzeit später als die Abschaltzeit ist, falls ja, wird $spaet ztu $true ####

if($heute.Hour -ge $abschaltZeit){
    $spaet=$true
}
else{
    $spaet=$false
}

#### Falls $success oder $spaet gesetzt ist, werden die PCs angesprochen und gegebenenfalls Heruntergefahren ####

if($success -Or $spaet){
    if($success){
           $log = "Es sind " +$ferien+" Computer fahren herunter"
           Out-File -FilePath 'H:\Skripte\logXtreme.txt' -Append -InputObject $log


    }
    if($spaet){
           $log = "Alle Computer werden um " + $heute + " heruntergefahren"
           Out-File -FilePath 'H:\Skripte\logXtreme.txt' -Append -InputObject $log
    }

    foreach($groupname in $raumGruppen){
        $a = (New-Object System.DirectoryServices.DirectoryEntry((New-Object System.DirectoryServices.DirectorySearcher("(&(objectCategory=Group)(name=$($groupname)))")).FindOne().GetDirectoryEntry().Path)).member | % { (New-Object System.DirectoryServices.DirectoryEntry("LDAP://"+$_)) } | Sort-Object sAMAccountName | SELECT @{name="User Name";expression={$_.Name}},@{name="User sAMAccountName";expression={$_.sAMAccountName}}
        foreach($as in $a)
        {
            Stop-Computer -ComputerName $as.'User Name' -force -ErrorAction SilentlyContinue
        }
    }
}


### Prüft für jeden PC in der RaumGruppe $raumGruppenUntis, ob er einen Eintrag im Stundenplan hat. Für gefundene PCs wird geprüft, ob der Unterricht gerade vorbei ist und dann gegebenenfalls heruntergefahren ###

foreach($groupname in $raumGruppenUntis){
    $a = (New-Object System.DirectoryServices.DirectoryEntry((New-Object System.DirectoryServices.DirectorySearcher("(&(objectCategory=Group)(name=$($groupname)))")).FindOne().GetDirectoryEntry().Path)).member | % { (New-Object System.DirectoryServices.DirectoryEntry("LDAP://"+$_)) } | Sort-Object sAMAccountName | SELECT @{name="User Name";expression={$_.Name}},@{name="User sAMAccountName";expression={$_.sAMAccountName}}
    foreach($as in $a)
    {
### Das Skript Pythonskript raumabfrage.py wird mit einem Raum als Parameter aufgerufen und leifert den Stundenplan dieses Raumes für den heutigen Tag zurück ###
        $untisAusgabe = python 'H:\Skripte\raumabfrage.py' $as.'User Name'
        if($untisAusgabe.Contains('datetime')){
            write-host "PC " $as.'User Name' " in Untis Gefunden"
            $stelleZeit = $untisAusgabe.LastIndexOf('datetime.time')+14
            $stelleDatum = $untisAusgabe.LastIndexOf('datetime.date')+14
            $a = $untisAusgabe.Substring($stelleDatum, $untisAusgabe.Substring($stelleDatum).IndexOf(')'))+", "+$untisAusgabe.Substring($stelleZeit, $untisAusgabe.Substring($stelleZeit).IndexOf(')'))
            $b = $a.Split(",")
            $a=$null
            foreach($bs in $b){
                $bs = $bs.Trim()
                $bs = [int]$bs
                $bs = $bs.ToString("00")
                $a+=$bs
            }
            $endZeit=[datetime]::ParseExact($a,'yyyyMMddHHmm',$null).AddMinutes(45)
            Write-Host "Letzte Stunde " $endZeit
            $heute = Get-Date
            Write-Host "Aktuelle Zeit " $heute
            if(($endZeit.AddMinutes(10) -lt $heute)-and($endZeit.AddMinutes(40) -gt $heute))
            {

                Stop-Computer -ComputerName $as.'User Name' -force -ErrorAction SilentlyContinue
                Write-Host "Der Computer " $as.'User Name' " wird heruntergefahren"
                $log = 'PC '+ $as.'User Name' + ' wurde um ' + $heute + ' heruntergefahren, letzte Stunde endete ' + $endZeit
                Out-File -FilePath 'H:\Skripte\logXtreme.txt' -Append -InputObject $log
            }
            


        }
    }
}

Python 

(Skript erwartet Bezeichnung des Raumes als Parameter)

import webuntis
import datetime
import sys
s = webuntis.Session(
    server='https://zensiert.webuntis.com',
    username='benutzer',
    password='passwort',
    school='schulname',
    useragent='Kontaktdaten des Administrators'
)
s.login()
today = datetime.date.today()
room = s.rooms().filter(name=sys.argv[1])[0]
table = s.timetable(room=room, start=today, end=today).to_table()
print (table)

s.logout()


Nachtrag: 
Bei weiteren Recherchen habe ich zwei weitere nützliche Skripte entdeckt:

  1. WebUntis direkt über Powershell
  2. Sitzungsanzeige über die Powershell 

Beide Quellen werde ich versuchen in meine Anleitung zum Herunterfahren in Zukunft einzuarbeiten. 



Keine Kommentare:

Kommentar veröffentlichen

Hinweis: Nur ein Mitglied dieses Blogs kann Kommentare posten.