Montag, 3. Dezember 2018

Kyocera Treiber

Da Kyocera Treiber veröffentlicht, welche nicht von Samba akzeptiert werden bleibt oft nur ein abändern der Treiber (Wie auch hier im Blog beschrieben).
Ein freundlicher Mensch hat einen Kyocera Treiber passend abgeändert und danach Microsoft Zertifiziert.
Bei Kyocera-Druckern eine absolute Empfehlung:
https://downloads.van-belle.nl/samba4/Kyocera/KyoClassicUniversalPCL5_v3.0_Samba_WHQL.zip

Donnerstag, 15. November 2018

Drucker anmelden per Gruppenrichtlinie

Da die Druckerzuweisung an den Clients nicht so zuverlässig funktioniert wie ich es mir wünsche habe ich ein neues Skript geschrieben, welches die Druckereinstellungen der Schulkonsole als Gruppenrichtlinie anlegt. Diese Richtlinie läuft in meiner Schule nun parallel zum herkömmlichen Drucker Anmeldeskript. Außerdem habe ich für Notfälle auf den Desktop eine Verknüpfung "Drucker reparieren" angelegt, welche gpupdate.exe startet.

Bisher habe ich keine Drucker-Anmeldeprobleme mehr feststellen können, aber falls doch Probleme auftreten sollten löst ein gpupdate dieses nun direkt. 

Wichtig: Mit dem Skript wird die printers.xml aus einer Gruppenrichtlinie bearbeitet. Dazu müssen einzigartige GUID-Schlüssel erzeugt werden. Diese gibt es erst ab Powershell 5! Bitte gegebenenfalls updaten oder das Skript auf einem Windows 10 Rechner starten. 

Leider braucht das Skript eine Vorhandene GPO als Vorlage. Auf anfrage kann ich diese gerne versenden. Ihr könnt euch aber auch selbst eine Richtlinie basteln. Die Anleitung dazu folgt in kürze. 

Und hier geht es los:

$zielGPO = "paedMLL_Druckerverbinden"
#Wenn Force = $true ist wird die GPO bei jedem Durchlauf aktualisiert, sonst nur bei neuen Printerlist Dateien oder fehlender GPO. 
$force = $false

#Hilfsfunktion. Dank an den Author Adam Bell
function Get-SID{
    Param ($DSIdentity)
$ID = new-object System.Security.Principal.NTAccount($DSIdentity)
return $ID.Translate( [System.Security.Principal.SecurityIdentifier] ).toString()
# From Adam Bell http://www.leadfollowmove.com/archives/powershell/security-identifiers-sids-and-nt-account-name
}

#Alle printer-assignment Dateien werden eingelesen
$a= Import-Csv -Header A,B,C -Delimiter ":" -Path  (Get-ChildItem -Path "\\server\netlogon\univention-printer-assignment\" -Filter '*.printerlist').FullName# | % -begin {$i=0} -process {$_.B=$i; $i++ }
$i=0
#Druckerreiheinfolge wird vor dem Sortieren gesichert
foreach($as in $a){
    $as.B = $i
    $i++
}

#Doppenungen werden entfernt und Daten aufbereitet
# Danke an https://stackoverflow.com/questions/31343752/how-can-you-select-unique-objects-based-on-two-properties-of-an-object-in-powers
$csvDataUnique = $a |   Group-Object 'A','C' |   %{ $_.Group | Select 'A','C','B' -First 1} |   Sort 'C'
$csvDataUnique | foreach{$_.C=$_.C.Split(",")[0].Split("=")[1]}
$backup = $csvDataUnique
$GPOListe = $csvDataUnique |   Group-Object 'C' |   %{ $_.Group | Select 'C' -First 1} |   Sort 'C'
$GPOListe=$GPOListe.C

#Liste aller Räume und deren Drucker wird erstellt.
$DruckerListe=@()
foreach($GPO in $GPOListe){
    $sammlung=@()
    foreach($zuordnung in $csvDataUnique){
        if($GPO -eq $zuordnung.C){
            $item = New-Object -TypeName psobject
            $item | Add-Member -MemberType NoteProperty -Name DruckerName -Value $zuordnung.A
            $item | Add-Member -MemberType NoteProperty -Name DruckerReihenfolge -Value $zuordnung.B
            $item | Add-Member -MemberType NoteProperty -Name DruckerNummer -Value 0
            $sammlung+=$item
            }        
    }
    $DruckerAdd = New-Object -TypeName psobject
    $DruckerAdd | Add-Member -MemberType NoteProperty -Name Name -Value $GPO
    $DruckerAdd | Add-Member -MemberType NoteProperty -Name Server -Value "\\SERVER\"
    $DruckerAdd | Add-Member -MemberType NoteProperty -Name GruppenName -Value ("PAEDML-LINUX\$GPO")
    $DruckerAdd | Add-Member -MemberType NoteProperty -Name GruppenSID -Value (Get-SID $GPO)
    $DruckerAdd | Add-Member -MemberType NoteProperty -Name Id -Value ""
    $DruckerAdd | Add-Member -MemberType NoteProperty -Name DruckerZuordnung -Value $sammlung
    $DruckerListe+=$DruckerAdd
}
#Drucker werden wieder in Reihenfolge gebracht.
foreach($GPO in $DruckerListe){
    $GPO.DruckerZuordnung = $GPO.DruckerZuordnung | Sort-Object -Property DruckerReihenfolge
    $i=0
    while($i -le $GPO.DruckerZuordnung.Count-1){
        $GPO.DruckerZuordnung[$i].DruckerNummer = $i
        $i++
    }
}
#Es wird geprüft, ob die GPO bereits existiert. Sonst wird Sie importiert und mit der OU Schule verknüpft. 
try{
    $ignore = Get-GPO -Name $zielGPO -ErrorAction Stop}
catch{
    $result = import-gpo -BackupId 13F02EBD-2DAA-44B9-B31F-743FC7E91F4E -TargetName $zielGPO -path "\\backup\opsi_depot_rw\DruckerSetup" -CreateIfNeeded
    New-GPLink -name $zielGPO -target "ou=schule,dc=paedml-linux,dc=lokal" -LinkEnabled Yes -ErrorAction SilentlyContinue
    $force=$true
}
$Id=(Get-GPO -Name $zielGPO).Id
$file = '\\BACKUP\opsi_depot_rw\DruckerSetup\{13F02EBD-2DAA-44B9-B31F-743FC7E91F4E}\DomainSysvol\GPO\User\Preferences\Printers\Printers.xml'
$target = '\\Server\sysvol\paedml-linux.lokal\Policies\{'+$Id+'}\User\Preferences\Printers\Printers.xml'

[xml]$xdoc = Get-Content ($file)
foreach($Raum in $DruckerListe){
#Für jeden Raum werden nun die Drucker Aktionen hinzugefügt. 
    $Raum.Id=$result.Id
    $server=$Raum.server
    #Alle Drucker aus der Schulkonsole werde ersteinmal entfernt, so werden falsche zuordnungen oder Probleme mit der Druckersperre gelöst. 
    foreach($Drucker in $Raum.DruckerZuordnung){
        $BranchToClone = @($xdoc.Printers.SharedPrinter)[1].Clone()
        $BranchToClone.Properties.path= $server+$Drucker.DruckerName
        $BranchToClone.name= $Drucker.DruckerName
        $BranchToClone.status=$Drucker.DruckerName
        $newguid = [System.Guid]::NewGuid().toString()
        $BranchToClone.uid= "{" + "$newguid" + "}"
        $CurrentDateTime = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        $BranchToClone.Changed = "$CurrentDateTime"
        $ignore = $xdoc.Printers.AppendChild($BranchToClone)
    }
    #Nun werden alle Drucker angelegt, jeweils mit einer Zielgruppenadressierung für den jeweiligen Raum. 
    foreach($Drucker in $Raum.DruckerZuordnung){
        $BranchToClone = @($xdoc.Printers.SharedPrinter)[2].Clone()
        $newguid = [System.Guid]::NewGuid().toString()
        $BranchToClone.uid= "{" + "$newguid" + "}"
        $CurrentDateTime = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
        $BranchToClone.Changed = "$CurrentDateTime"
        $BranchToClone.name=$Drucker.DruckerName
        $BranchToClone.status=$Drucker.DruckerName
        $BranchToClone.Filters.FilterGroup.name = $Raum.GruppenName
        $BranchToClone.Filters.FilterGroup.sid = $Raum.GruppenSID
        if($Drucker.DruckerNummer-eq 0){$BranchToClone.Properties.default="1"}
        $BranchToClone.Properties.path=$server+$Drucker.DruckerName
        $ignore = $xdoc.Printers.AppendChild($BranchToClone)
    }
}
#Die ersten 3 Knoten sind die Vorlagen-Knoten aus der Vorlage GPO
$nodedelete = $xdoc.Printers.SharedPrinter[0]
$ignore = $xdoc.Printers.RemoveChild($nodedelete)
$nodedelete = $xdoc.Printers.SharedPrinter[0]
$ignore = $xdoc.Printers.RemoveChild($nodedelete)
$nodedelete = $xdoc.Printers.SharedPrinter[0]
$ignore = $xdoc.Printers.RemoveChild($nodedelete)

#Änderungen werden nur angewendet, wenn Univention Printerlist Dateien vorliegen oder die GPO nicht vorhanden war. 
try{if($force -or ((Get-ChildItem "\\server\netlogon\univention-printer-assignment\" | Foreach {$_.LastWriteTime} | sort)[(Get-ChildItem "\\server\netlogon\univention-printer-assignment\" | Foreach {$_.LastWriteTime} | sort).Count-1] -gt (Get-Item $target| Foreach {$_.LastWriteTime}))){
        Write-Host "Neue Druckereinstellungen werden installiert."
        $xdoc.Save($target)
    }
    else{
        Write-Host "Keine neuen Druckereinstellungen gefunden."
    }
}
catch{
    Write-Host "Neue Druckereinstellungen werden installiert."
    $xdoc.Save($target)
}

Dienstag, 10. Juli 2018

Remote Desktop Connection Manager in der paedML Linux

Ich wurde gerade auf den Remote Desktop Connection Manager von Microsoft aufmerksam gemacht. Es ist eine Verwaltungssoftware für Remotedesktop Verbindungen (RDP) und bietet die Möglichkeit verschiedene RDP Verbindungen nach Gruppen sortiert in einem User Interface darzustellen und Verbindungen per Mausklick zu öffnen. Um diese Nutzen zu können, muss man mit Opsi das Paket rdp-zugriff auf allen Benötigten Clients auszurollen.
Mit dem Manager ist es nun möglich alle Aufgaben, welche lokal am Client per Adminaccout erledigt werden müssen, bequem per Remote Verbindung durchzuführen. Vor allem zur Installation von Programmen ohne Opsi-Pakete kann dies von großem Nutzen sein.

Da die paedML-Linux immer die gleichen Standardcontainer verwendet habe ich daher ein Kleines Skript zusammengestellt, welches die per Schulkonsole angelegte Computerraumstruktur direkt in den Manager importiert. Das Powershell Skript erstellt dann eine Datei "schule.rdg", welche direkt im Manager geöffnet werden kann.
Das Skript verwendet die hier zur Verfügung gestellten Methoden zum Anlegen der Struktur, vielen Dank an den Autor. Zum Ausführen das Skript in die Powershell kopieren und ausführen. Der Dateipfad/Name lässt sich über $pfad anpassen:

$pfad = ".\schule.rdg"
function New-RDCManFile
{
  Param(
    [Parameter(Mandatory = $true)]
    [String]$FilePath,
    
    [Parameter(Mandatory = $true)]
    [String]$Name
  )
  BEGIN
  {
    [string]$template = @' 
<?xml version="1.0" encoding="utf-8"?> 
<RDCMan programVersion="2.7" schemaVersion="3"> 
  <file> 
    <credentialsProfiles /> 
    <properties> 
      <expanded>True</expanded> 
      <name></name> 
    </properties> 
  </file> 
  <connected /> 
  <favorites /> 
  <recentlyUsed /> 
</RDCMan> 
'@ 
    $FilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePath)
    if(Test-Path -Path $FilePath)
    {
      Write-Error -Message 'File Already Exists'
    }
    else
    {
      $xml = New-Object -TypeName Xml
      $xml.LoadXml($template)
    }
  }
  PROCESS
  {
    $File = (@($xml.RDCMan.file.properties)[0]).Clone()
    $File.Name = $Name
    
    $xml.RDCMan.file.properties |
    Where-Object -FilterScript {
      $_.Name -eq ''
    } |
    ForEach-Object -Process {
      [void]$xml.RDCMan.file.ReplaceChild($File,$_)
    }
  }
  END
  {
    $xml.Save($FilePath)
  }
}
function New-RDCManGroup
{
  Param(
    [Parameter(Mandatory = $true)]
    [String]$FilePath,
    
    [Parameter(Mandatory = $true)]
    [String]$Name
  )
  BEGIN
  {
    $FilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePath)
    if(Test-Path -Path $FilePath)
    {
      $xml = New-Object -TypeName XML
      $xml.Load($FilePath)
    } 
    else
    {
      Write-Error -Exception $_.Exception
      throw $_.Exception
    }
  }
  PROCESS
  {
    $group = $xml.CreateElement('group')
    $grouproperties = $xml.CreateElement('properties')      
    $groupname = $xml.CreateElement('name')
    $groupname.set_InnerXML($Name)     
    $groupexpanded = $xml.CreateElement('expanded')
    $groupexpanded.set_InnerXML('False')      
    [void]$grouproperties.AppendChild($groupname)
    [void]$grouproperties.AppendChild($groupexpanded) 
    [void]$group.AppendChild($grouproperties)
    [void]$xml.RDCMan.file.AppendChild($group)
  }
  END
  {
    $xml.Save($FilePath)
  }
}
function New-RDCManServer
{
  Param(
    [Parameter(Mandatory = $true)]
    [String]$FilePath,   
    [Parameter(Mandatory = $true)]
    [String]$GroupName,
    [Parameter(Mandatory = $true)]
    [String]$Server,
    [Parameter(Mandatory = $true)]
    [String]$DisplayName
  )
  BEGIN
  {
    $FilePath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($FilePath)
    if(Test-Path -Path $FilePath)
    {
      $xml = New-Object -TypeName XML
      $xml.Load($FilePath)
    } 
    else
    {
      Write-Error -Exception $_.Exception
      throw $_.Exception
    }
  }
  PROCESS
  {
    $ServerNode = $xml.CreateElement('server')
    $serverproperties = $xml.CreateElement('properties')
    $servername = $xml.CreateElement('name')
    $servername.set_InnerXML($Server)   
    $serverdisplayname = $xml.CreateElement('displayName')
    $serverdisplayname.set_InnerXML($DisplayName) 
    [void]$serverproperties.AppendChild($servername)
    [void]$serverproperties.AppendChild($serverdisplayname)
    [void]$ServerNode.AppendChild($serverproperties)
    $group = @($xml.RDCMan.file.group) | Where-Object -FilterScript {
      $_.properties.name -eq $groupname
    } 
    [void]$group.AppendChild($ServerNode)
  }
  END
  {
    $xml.Save($FilePath)
  }
}
New-RDCManFile -FilePath $pfad -Name Schule 
New-RDCManGroup -FilePath $pfad -Name "Admin"
New-RDCManServer -FilePath $pfad -DisplayName "AdminVm" -Server "AdminVm" -Group "Admin"
[ADSI]$domain = "LDAP://CN=raeume,CN=groups,OU=schule, DC=paedml-linux,DC=lokal"
$alleGruppen = ($domain.Children.distinguishedName | ForEach-Object {[ADSI]"LDAP://$_"}).cn
foreach($gruppe in $alleGruppen){
    New-RDCManGroup -FilePath $pfad -Name $gruppe
    $pcsInGruppe = (New-Object System.DirectoryServices.DirectoryEntry((New-Object System.DirectoryServices.DirectorySearcher("(&(objectCategory=Group)(name=$($gruppe)))")).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($client in $pcsInGruppe){
            write-host $client.'User Name' "wird zu" $gruppe "hinzugefügt"
            $client = $client.'User Name'
            New-RDCManServer -FilePath $pfad -DisplayName $client -Server $client -Group $gruppe
        }
}
Da die Schulstruktur nun angelegt ist kann die erstellte Datei per Doppelklick im Remote Desktop Connection Manager geöffnet werden. Um mehrere Client-PCs verwalten zu können kann es sinnvoll sein zeitweise das Admin Passwort zu hinterlegen. Damit sind die gewünschten PCs nur noch einen Doppelklick von der Verwaltung entfernt. Klicken Sie Dazu mit der rechten Maustaste auf
"Schule" --> "Properties"



Dort wählen Sie die Logon Credentials, deaktivieren "Inherit..." und können nun das Passwort hinterlegen und mit OK bestätigen. Über "File" --> "save schule.rdg" kann das Passwort (verschlüsselt) auch auf dauer in der Datei hinterlegt werden.


Wenn Sie mit dem Administrieren der Remote-PCs fertig sind kann es sinnvoll sein das Passwort an dieser Stelle wieder zu entfernen, da diese Datei den Zugriff auf alle Client-PCs der Schule ermöglicht.

Montag, 2. Juli 2018

Opsi mit Powershell steuern

Der Opsi Config Editor ist ein sehr mächtiges Werkzeug. In meiner Schule haben wir allerdings über 300 Clients was dem Editor zu schaffen macht. Das setzen von Produktkonfigurationen für ganze PC-Räume kann daher etwas an den Nerven nagen.
Opsi bietet aber auch eine Web-API für die Steuerung per Skript.
Ich werde hier auf deren Steuerung per Powershell eingehen.

Um einen Befehl an Opsi zu senden verwende ich die "Invoke-RestMethod" Cmdlet.
Dieser muss die Adresse von Opsi, eine Authentifizierung und der Opsi-Aufruf mitgegeben werden.
Invoke-RestMethod -uri $Adresse -headers $Benutzer -method post -body $Aufruf
Die Adresse ist ein String mit der Adresse der API,
$Adresse = 'https://10.1.0.2:4447/rpc'
Für die Authentifizierung kann der Administrator oder der schoolopsiadmin verwendet werden. Das Passwort des letzteren ist auf dem Server in /etc/schoolopsiadmin.secret abgelegt. Leider gibt es meines Wissens keine Möglichkeit das Passwort per Powershell abzufragen, daher liegt das Passwort in meinem Skript zusätzlich in "H:\Skripte\opsiadmin.txt".

$pair = "$('schoolopsiadmin'):$(Get-Content H:\Skripte\opsiadmin.txt)"
$encodedCreds = ([System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair)))
$Benutzer = @{Authorization = "Basic $encodedCreds"}
Mit -method post wird angegeben, dass etwas an den Server gesendet wird.
Letztlich folgt der Body. Dieser Parameter ist im JSON Format zu übergeben und kann daher direkt als Powershell-Ojekt erstellt werden und dann zu JSON übersetzt werden. Um Befehle zu testen kann man den interaktiven Modus der Opsi-Api über https://10.1.0.2:4447/interface verwenden. Da die meisten Aufrufe Attribute und Filter erlauben hier eine Vorlage:
$Aufruf = ConvertTo-Json(@{ method = "Methode";params = @(@("optionalerParameter1", "oParam2"), @{FilterAttribut1 = "Wert1", FilterA2 = "Wert2"});id = 1}))
In der Praxis kann dies Um auf dem Client  pc1 das Paket 7zip auf setup zu stellellen:
$Aufruf = ConvertTo-Json( @{ method = "setProductActionRequest"; params = @("7zip", "pc1.paedml-linux.lokal", "setup"); id = 1})
Um die Installierten Pakete auf pc1 von Opsi zu erfragen
$Aufruf = ConvertTo-Json(@{ method = "productOnClient_getObjects";params = @(@(), @{clientId = "pc1.paedml-linux.lokal"});id = 1}))
Natürlich können anstelle von Texten auch Variablen benutzt werden.
Um bei einem Computerraum alle installierten Produkte zu aktualisieren muss eine Abfrage an das Opsi-Depot gestellt werden und die Paketversionen mit denen auf den Clients vergleichen werden. Dies kann für alle Computer aus einer Raumguppe durchgeführt werden. Das fertige Skript könnte z.B. so aussehen:
$computerraum= 'schule-raum1'
$nachInstallationHerunterfahren=$true
$ClientListe=((New-Object System.DirectoryServices.DirectoryEntry((New-Object System.DirectoryServices.DirectorySearcher("(&(objectCategory=Group)(name=$($computerraum)))")).FindOne().GetDirectoryEntry().Path)).member | % { (New-Object System.DirectoryServices.DirectoryEntry("LDAP://"+$_)) } | Sort-Object sAMAccountName | SELECT @{name="User Name";expression={$_.Name}},@{name="User sAMAccountName";expression={$_.sAMAccountName}})."User Name"
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
$nichtUpdaten = {"ms_office", "set-registry-keys", "rdp-zugriff", "shutdownwanted", "windomain", "windows10-upgrade", "ms-sql-2012ee"}
#Login Variablen
$urlJSON = 'https://10.1.0.2:4447/rpc'
$user = "schoolopsiadmin"
$pass = Get-Content H:\Skripte\opsiadmin.txt
$pair = "$($user):$($pass)"
$encodedCreds = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))
$basicAuthValue = "Basic $encodedCreds"
$Headers = @{
    Authorization = $basicAuthValue
}
Write-Host "Frage Depot ab"
$Depot = (Invoke-RestMethod -uri $urlJSON -Headers $Headers -method post -body (ConvertTo-Json(@{ method = "productOnDepot_getObjects";params = @();id = 1}))).result
foreach($zielClient in $ClientListe){
    $zielClient = ($zielClient+=".paedml-linux.lokal").ToLower()
    Write-Host "Frage Client" $zielClient "ab"
    $Client = (Invoke-RestMethod -uri $urlJSON -Headers $Headers -method post -body (ConvertTo-Json(@{ method = "productOnClient_getObjects";params = @(@(), @{clientId = $zielClient});id = 1}))).result
    #ProductOnDepot  - $pd
    #ProductOnClient - $pc
    $updateFlag=$false
    foreach($pd in $Depot){
        foreach($pc in $Client){
            if(($pd.productId -eq $pc.productId) -and ($pc.installationStatus -eq "installed") -and ($pc.productType -eq "LocalbootProduct") -and (-not($nichtUpdaten -match $pc.productId)) -and ((-not $pc.productId.StartsWith("opsi"))-or ($pc.productId -eq "opsi-client-agent")-or ($pc.productId -eq "opsi-configed"))){
                Write-Host
                Write-Host "Client:" $zielClient
                Write-Host $pd.productId -ForegroundColor Yellow
                Write-Host "    Client Version: "$pc.productVersion
                Write-Host "    Depot  Version: "$pd.productVersion
                if(($pc.productVersion -eq $pd.productVersion)-and ($pc.packageVersion -eq $pd.packageVersion)){
                    Write-Host "--> kein Update notwendig" -ForegroundColor Green
                }
                else{
                    $updateFlag=$true
                    if((($pc.productVersion -eq $pd.productVersion)) -and ($pc.packageVersion -ne $pd.packageVersion)){Write-Host "--> neues Paket vorhanden, " -ForegroundColor Red}
                    Write-Host "--> neue Version wird auf setup gesetzt. " -ForegroundColor Red
                    $Produkt = $pd.productId
                    #$strAction= (ConvertTo-Json(@{ method = "setProductActionRequest";params = @($Produkt, $zielClient, "setup");id = 1}))
                    $ignoreOutput = Invoke-RestMethod -uri $urlJSON -Headers $Headers -method post -body (ConvertTo-Json(@{ method = "setProductActionRequest";params = @($Produkt, $zielClient, "setup");id = 1}))
                }
            }
        }
    }
    if($updateFlag){
        if($nachInstallationHerunterfahren){
            write-host "Nach Installation" $zielClient "herunterfahren"
            $ignoreOutput = Invoke-RestMethod -uri $urlJSON -Headers $Headers -method post -body (ConvertTo-Json(@{ method = "setProductActionRequest";params = @("shutdownwanted", $zielClient, "once");id = 1}))
        }
        write-host "Update wird gestartet"
        $ignoreOutput = Invoke-RestMethod -uri $urlJSON -Headers $Headers -method post -body (ConvertTo-Json(@{ method = "hostControlSafe_fireEvent";params = @("on_demand", $zielClient);id = 1}))
    }
    else{Write-Host "Für" $zielClient "wurden keine Updates gefunden."}
}
Das Skript sucht zuerst alle Clients des PC-Raumes, welcher unter $computerraum angegeben wird. Nun prüft das Skript jeden Computer ob die Versionen installierter Programme von denen im Depot abweichen. Falls ja, werden die Produkte auf "setup" gesetzt und eine Installation "on_demand" ausgeführt. Ebenso wird "shutdownwanted auf "once" gesetzt, falls dies nicht mit der Variable $nachInstallationHerunterfahren=$false unterbunden wird.
D.h. das Skript arbeitet alle PCs des Raumes ab, startet die Installation und fährt die PCs im Anschluss herunter.

Montag, 25. Juni 2018

"Vertrauen Sie diesem Drucker"-Warnungen beseitigen

Bei der regulären Einrichtung von Druckern bekommen Clients nach dem Anmelden immer wieder die "Vertrauen Sie diesem Drucker"-Warnung. Um den Windows-CLients das Vertrauen wieder zu geben müssen Sie den Clients den Druckerserver per GPO bekannt machen. (Quelle)

Öffnen Sie dazu den Gruppenrichtlinien Editor auf der AdminVM. (Start --> "gpmc.msc")
Erstellen und verknüpfen Sie bei der OU "schule" eine neue Gruppenrichtlinie mit einem Namen, z.B. "Einstellungen Drucker".
Mit einem rechtsklick auf die neu erstellte Richtlinie und einem Klick auf "Bearbeiten" öffnen Sie den Gruppenrichtlinienverwaltungs-Editor. Öffnen Sie hier
Computerkonfiguration --> Richtlinien --> Administrative Vorlagen --> Drucker.
 Wählen Sie aus der rechts angezeigten Liste von Einstellungen "Point-and-Print-Einschränkungen" mit einem Doppelklick aus. Dort müssen Sie "Aktiviert" anklicken. Setzen Sie ein Häckchen bei "Benutzer können Point-und-Print nur mit folgenden Servern verwenden. Bei "Vollqualifizierte Servernamen" tragen Sie

server.paedml-linux.lokal 

ein.
Falls Sie auch einen anderen Print-Server verwenden, wie z.B. in meinem Artikel über die AdminVM als Druckerserver tragen Sie diesen durch ein Semikolon getrennt ebenfalls ein.
server.paedml-linux.lokal; adminvm.paedml-linux.lokal
Nun setzen Sie noch die Sicherheitshinweise jeweils auf "nicht anzeigen".Bestätigen Sie die Einstellungen mit rechts unten mit "OK".

Mit einem Doppelklick auf "Point-und-Print für Pakete" öffen Sie nun eine zweite Einstellung.
Klicken Sie hier ebenfalls auf "Aktivieren". Bei den Servernamen klicken Sie auf "Anzeigen" und tragen bei "Inhalte anzeigen" den Server und gegebenenfalls alternative Druckerserver an.
server.paedml-linux.lokal
 

Mit einem Klick auf "OK" ist die Richtlinie fertig.
Denken Sie daran, dass Gruppenrichtlinien teilweise erst mit einer Verzögerung am Client eingelesen werden. Um die Resultate sofort Prüfen zu können können Starten Sie den Client neu und melden sich als Administrator an. Geben Sie dann
gpupdate /force

ein um die Richtlinien neu zu laden. 

Dienstag, 5. Juni 2018

SMART Notebook Proxyfenster beseitigen

Die digitalen White-Boards unserer Schule verwenden die Software SMART Notebook. Die Software öffnet nach dem Starten jedoch ein Login-Fenster für die Proxy-Einstellungen. Dieses Fenster muss dann vor jeder Benutzung der digitalen Tafel 3 mal weggeklickt werden. Um diesen Umstand zu ändern muss man eine Außnahmeregelung mi Proxyserver für den jeweiligen PC anlegen.

Vorgehen:


Öffnen Sie Putty, verbinden sich zum Server (10.1.0.1) und melden sich als root an. Navigieren Sie in das Verzeichnis /etc/squid3
cd /etc/squid3/
und machen Sie eine Backup-Kopie der Datei local.conf
cp local.conf local.conf.bak
Nun editieren Sie die Datei local.conf mit einem Editor Ihrer Wahl, z.B. mc oder vi.
Dort fügen Sie pro Client oder IP-Range (Falls alle freizuschaltenden Cliengts in einem IP Bereich liegen) die Zugriffe ohne Proxy authentifizierung frei. 
acl SMART1 src 10.1.0.55/32
http_access allow SMART1
SMART1 ist ein Pseudonym für die Regel, es könnte auch ein anderer Name gewählt werden. Die IP-Adresse ist die des PCs auf dem die Smart-Software freigeschaltet werden soll. Die IP können Sie aus der Schulkonsole /Rechner(Schulen) entnehmen. Das /32 bedeutet, dass nur die Angegebene IP freigeschaltet wird. Falls sich mehrere freizuschaltende Rechner in einem IP-Bereich befinden kann auch ein ganzer Bereich freigeschaltet werden. Verwenden Sie z.B. die Subnet-Calculator.com um einen geeigneten Bereich zu bestimmen. 

Nach dem Speichern der Datei sollten Sei den Squid Service neu starten. 
service squid3 restart
Die freigeschalteten Rechner sollten nun keine Anmeldung mehr benötigen. 
Vorsicht: Eine INTERNETSPERRE kann an diesem PC nun NICHT MEHR aktiviert werden! Der Jugendschutzfilter ist davon jedoch nicht beeinflusst. 

Beispiele:


Das Freischalten mehrer PCs kann z.B. so aussehen. 
acl SMART1 src 10.1.0.55/32
acl SMART2 src 10.1.0.68/32
acl SMART3 src 10.1.0.53/32
acl SMART4 src 10.1.0.99/32
http_access allow SMART1
http_access allow SMART2
http_access allow SMART3
http_access allow SMART4
Wenn die freizuschaltenden PCs die IP-Adressen 10.1.0.201 bis  10.1.0.206 sind kann der Eintrag auch wie folgt aussehen, siehe Subnet-Calculator.com:
acl SMART src 10.1.0.201/29
http_access allow SMART

Druckerinstallation OHNE Probleme ABER OHNE Schulkonsolensteuerung

Vorwort:


Die Verteilung von Druckern über die Schulkonsole bietet einige Vorteile, wie die zentrale Moderation von Druckern, Druckerwarteschlange usw. Die Installation über den SERVER bringt aber auch viele Probleme mit sich, Treiber, welche sich nicht hochladen/installieren lassen. Warnungen wie „Vertrauen Sie diesem Drucker“, deren Beseitigung ich hier beschreibe, usw.

Eine Alternative dazu kann daher sein, die AdminVM als Druckserver einzusetzen. Dies bietet gegenüber der Server-Verteilung ebenfalls Vorteile: Treiber werden ohne Fehler installiert und an Clients verteilt. Die Drucker können ins Druckerverzeichnis aufgeführt und von jedem Benutzer „hinzugebucht“ werden. Die Verteilung der Drucker kann z.B. über Gruppenrichtlinien gelöst werden.
VORSICHT: Einstellungen der Schulkonsole sind völlig unabhängig der hier gemachten Einstellungen, damit verlieren Sie die Kontrolle der Drucker über die Schulkonsole.

Nachtrag: Ich habe gerade einen Artikel bei Microsoft entdeckt, der relativ exakt die gleichen Schritte für den Windows Server 2012 beschreibt. Das hätte mir viel Zeit und Nerven erspart. Damit die Seite nicht nochmal verloren geht, hier der Link: Drucker auf Windows Server 2012.

Vorgehen: 


  1. AdminVM als Druckserver einrichten,
  2. Freigeben der Drucker über Gruppenrichtlinien,
  3. Setzten der Standarddrucker über Gruppenrichtlinien

1. AdminVM als Druckserver einrichten


Die Einrichtung läuft analog zum Administratorhandbuch Kapitel 6.5.
Laden Sie vorbereitend für Ihren Drucker die aktuellsten Treiber für Ihre Windows-Version herunter und entpacken Sie ihn, z.B. mit 7Zip.

Auf der AdminVM drücken Sie die Windows-Taste und öffnen Sie den „Ausführen“-Dialog. In dem sich neu öffnenden Fenster geben Sie „printmanagement.msc“ ein und drücken Sie anschließend auf „OK“. Es öffnet sich das Fenster „Druckerverwaltung“.


Dort ist bereits die ADMINVM (lokal) hinterlegt, da jeder Windows PC automatisch einen Druckerserver installiert hat. Wenn Sie von einem anderen Windows PC aus Arbeiten wollen können Sie printmanagement.msc auch von einem anderen Windows Client aus öffnen. Dazu geben Sie analog zum Handbuch für Administratoren Kapitel 6.5.1 den Namen des Druckerservers an, also ADMINVM oder 10.1.0.13 (Die IP-Adresse der AdminVM)

Klicken Sie in der Druckverwaltung auf ADMINVM und auf Drucker.

Mit einem Rechtsklick auf „Drucker“ können Sie „Drucker hinzufügen“ wählen.


Klicken Sie „Einen neuen TCP/IP- oder Webdienste-Drucker über IP Adresse oder Hostnamen hinzufügen“ und anschließend auf „Weiter“.


Stellen Sie den Gerätetyp auf TCP-IP-Gerät und Hostname oder IP-Adresse auf die Adresse des Druckers. Diese können Sie z.B. aus der Schulkonsole entnehmen. (Schulkonsole/Drucker/Druckername enthält die IP des Druckers)


Falls Sie einen völlig neuen Drucker anlegen wollen müssen Sie wie üblich diesem über die Schulkonsole über „Rechner (Schulen)“ → „Hinzufügen“ → „Gerät mit IP-Adresse“ dem Drucker eine IP-Adresse zuweisen.

Beim Anschlussname können Sie den Druckernamen des neuen Druckers eingeben. Mit einem Klick auf „Weiter“ wird der Drucker nun angesprochen und erkannt.


Im folgenden Fenster klicken Sie auf „Einen neuen Treiber installieren“ und auf „Weiter“.

Klicken Sie auf „Datenträger“, „Durchsuchen“ und wählen Sie den Ordner des Druckertreibers. Im Ordner müssen Sie die „inf-Datei“ finden und anklicken, diese befindet sich oft in einem Ordner mit einer Bezeichnung „x64“ oder „64bit“. Nach einem Klick auf „Weiter“ wird eine Liste von Druckern angezeigt, welche im Treiber vorhanden sind. Wählen Sie wenn möglich den Treiber Ihres Druckers aus. Oft ist jedoch auch nur ein „Universaltreiber“ vorhanden, dieser sollte ebenfalls funktionieren. Nach einem Klick auf weiter werden die Freigabeeinstellungen geöffnet.
Geben Sie bei „Druckername“ und „Freigabename“ jeweils den Namen ein, unter welchem der Drucker im Netzwerk geführt werden soll.

Nach zwei letzten Klicks auf „Weiter“ wird der Drucker installiert und erstellt.

OPTIONAL: Jetzt können Sie den Drucker im Verzeichnis auflichten lassen. VORSICHT: durch diese Einstellung kann JEDER Benutzer im Netzwerk den Drucker auswählen und darauf drucken, ohne dass dies eingeschränkt werden kann. Mit einem Rechtsklick auf den Drucker erscheint im Menü „In Verzeichnis auflisten“, nach dem aktivieren kann der Drucker in der ganzen Schule gefunden werden.


klicken Sie dazu z.B. in Word bei der Druckerauswahl auf „Druckerhinzufügen…“.


2. Freigeben der Drucker über Gruppenrichtlinien


Das Freigeben von Druckern über Gruppenrichtlinien ist von Microsoft gut vorbereitet, jedoch ist aufgrund der vom Server vorgegebenen Struktur vorerst kein setzen des Standarddruckers möglich.

Klicken Sie mit rechter Maustaste auf den Drucker und wählen Sie „Mit Gruppenrichtlinie bereitstellen…“


Bei der Wahl des Gruppenrichtlinienobjekts klicken Sie auf „Durchsuchen…“. Klicken Sie mit einem Doppelklick die Organisationseinheit „schule.paedml-linux.lokal“ an und anschließend auf „Neues Gruppenrichtlinienobjekt erstellen“.


Geben Sie dem neuen Gruppenlinienobjekt einen Namen, welcher die Zuordnung von Druckern zu einem Computerraum wiederspiegelt, z.B. „Drucker Raum 15“ oder „Drucker Lehrerzimmer“.

Falls bei einem weiteren Drucker für den gleichen Raum bereits ein Gruppenrichtlinienobjekt existiert erstellen Sie kein neues. Die vorhandenen Richtlinien werden nach einem Klick auf „schule.paedml-linux.lokal“ aufgelistet, wählen Sie dort die passende Richtlinie aus.


Aktivieren Sie nun die Checkbox „Die Computer, auf die…“ und klicken Sie auf „Hinzufügen“. Damit ist der Drucker per Gruppenrichtlinie freigegeben.


Diese Richtlinie gilt vorerst für alle Computer von „schule.paedml-linux.lokal“. Dies muss nun eingeschränkt werden. Klicken Sie auf die Windows-Taste und geben Sie „gpmc.msc“ ein. (Dies ist nur als Administrator möglich).

Öffnen Sie „Gesamtstruktur“ → „Domänen“ → „paedml-linux.lokal“ → „schule“ und suchen Sie dort Ihre neue Richtlinie.


In der Sicherheitsfilterung muss nun „Authentifizierte Benutzer“ entfernt werden. Wählen Sie dazu diese aus und klicken Sie auf „Entfernen“.


In der Sicherheitsfilterung müssen nun alle Benutzer und Computer hinzugefügt werden, auf welche Druckereinstellungen wirken sollen, die Computer des Computerraumes, und alle Schuler und Lehrer.
Klicken Sie auf in der Sicherheitsfilterung auf „Hinzufügen“. Im Fenster „Benutzer, Computer oder Gruppe auswählen“ geben Sie den Namen des Computerraumes mit dem präfix „schule-“ ein, z.B. „schule-raum15“. Nach einem Klick auf Namen überprüfen wird die Raumgruppe erkannt. Bestätigen Sie mit „OK“.


Fügen Sie auf gleiche Weise die Gruppen „schueler-schule“ und „lehrer-schule“ hinzu.


3. Setzten der Standarddrucker über Gruppenrichtlinien


Zum setzten der Standarddrucker muss die Gruppenrichtlinie bearbeitet werden.
Klicken Sie mit der rechten Maustaste auf das Gruppenlinienobjekt und wählen Sie „Bearbeiten…“


Es öffnet sich der Gruppenlinienverwaltungs-Editor. Öffnen Sie hier die Struktur „Benutzerkonfiguration“ → „Einstellungen → „Systemsteuerungseinstellungen“ → „Drucker“. Mit einem Rechtsklick in den weißen Bereich wählen Sie „Neu“ → „Freigegebene Drucker“.


Klicken Sie die Checkbox „Drucker als Standarddrucker festlegen…“ an und wählen danach auf dem Knopf „ … “ den Standarddrucker des Raumes. Klicken Sie bei der Druckersuche dazu auf „Jetzt suchen“, da der Drucker jetzt im Verzeichnis aufgelistet ist wird er unten angezeigt. Bestätigen Sie mit „OK“.
VORSICHT: Wenn der Drucker nicht im Verzeichnis aufgelistet wurde müssen Sie den Freigabepfad von Hand eingeben, z.B. \\ADMINVM\Drucker5. 
Vielen Dank an Herrn Unmüssig für den Hinweis. 

Das Setzen des Standard Druckers darf nur in diesem Computerraum angewendet werden. Klicken Sie dazu oben auf den Reiter „Gemeinsame Optionen“ und klicken Sie „Zielgruppenadressierung auf Elementebene“ an. Wählen Sie dann die „Zielgruppenadressierung“. Im Zielgruppenadressierungseditor klicken Sie auf „Neues Element“ und wählen „Sicherheitsgruppe“.


Jetzt wählen Sie auf dem Knopf „ … “ den Computerraum aus, z.B. „schule-Raum15“ und klicken auf „Namen überprüfen“. Bestätigen Sie mit „OK“.
Aktivieren Sie den Auswahlknopf „Computer in der Gruppe“. Klicken Sie auf „OK“.


Damit sind die Drucker von einem Windows PC freigegeben, dem Computerraum werden zugeordnete Drucker zugeteilt und der Benutzer des Raumes bekommt einen Standarddrucker zugeordnet.

Montag, 4. Juni 2018

"Bitte deaktivieren Sie Ihren Adblocker!"

Ich wurde gerade informiert das beim Aufruf von einigen Webseiten aus der Schule ein "Bitte deaktivieren Sie Ihren Adblocker!" Fenster eingeblendet wird.

Die paedML Linux verwendet jedoch keine lokalen Adblocker weshalb das "deaktivieren" etwas schwer fällt. Im Whiltelisting der paedML "rumzumurksen" um "hoffentlich" diese Seiten sehen zu können ist ebenfalls nicht all zu leicht. Aber natürlich kann man auch Feuer mit Feuer bekämpfen. Die Installation von uBlock Origin, einem AdBlocker löst das Problem prompt. Wunderbar, jetzt muss ich gegen Adblocker Warunungen einen Adblocker installieren... Dies ist eigentlich nicht im Sinne des Erfinders, jedoch zur Zeit die einzig mir bekannte Möglichkeit diese Seiten aufrufbar zu halten. Wenn Sie weitere/bessere Möglichkeiten kennen entsprechende Seiten anzuzeigen, ohne deren Monetarisierung zu unterlaufen, würde ich mich über Feedback freuen.


Die Installation von uBlock Origins auf den Clients können Sie per Gruppenrichtlinie durchführen, wie in dieser Quelle beschrieben:

Erstellen oder öffnen Sie eine passende Gruppenrichtlinie.
Unter Benutzerkonfiguration --> Administrative Vorlagen --> Google Chrome --> Erweiterungen wählen Sie die Einstellung --> "Liste der Apps und Erweiterungen konfigurieren, deren Installation erzwungen wurde". Diese müssen Sie "Aktivieren" und unter "Anzeigen" die ID der Erweiterung und der Updateserver hinterlegen. Für uBlock Origin ist dies der Wert:
cjpalhdlnbpafiamejdnhcphjbkeiagm;https://clients2.google.com/service/update2/crx
Stellen Sie wie immer sicher, dass die Richtlinie von allen Computerkonten und Nutzern gelesen werden kann, z.B. mit "Authenticated Users" in welchem sowohl PC als auch Benutzerkonten liegen.

Montag, 7. Mai 2018

OpenVPN für IOS Remotezugriff auf Home-Verzeichnisse

Um auf die Homeshares aus dem Internet zugreifen zu können brauchen Sie einen funktionierenden OpenVPN Zugang. Die Anleitung dazu findet sich im Administratoren Handbuch in Kapitel 20. 

Wenn der Zugang funktioniert haben Sie bereits die zwei Dateien client.ovpn und ucs-root-ca.crt. Die erste Datei enthält die Adresse der Schule und die Konfiguration der Client Einstellungen, die zweite Datei ist das Stammzertifikat zur Authentifizierung des Servers.

OpenVPN Connect Vorbereitungen 

Um einen Zugriff für iOS Geräte herzustellen laden Sie bitte die kostenfreie App OpenVPN Connect herunter. Wenn Sie die client.ovpn und ucs-root-ca.crt über iTunes in den Ordner der App laden kann die Verbindung ohne weitere Arbeiten hergestellt werden.
Die App bietet auch die Möglichkeit die ovpn Datei über andere Quellen wie AirDrop oder eMail einzulesen, jedoch funktioniert dies nicht mit der Zertifikatsdatei.

Um über "einfachem" Wege die Zertifikatsdatei ebenfalls übertragen zu können kann das Zertifikat in die opvn Datei eingearbeitet werden. Die Anleitung dazu findet sich auch in der Hilfe der  OpenVPN Connect App.

Öffnen Sie die client.ovpn und ucs-root-ca.crt mit einem Texteditor.
Dort entfernt man die Zeile "ca ucs-root-ca.crt". Kopieren Sie den Inhalt der ucs-root-ca.crt und fügen Sie diesen am Ende der Datei client.ovpn ziwschen einem "ca" html Tag ein.

          client
          remote 123.4.56.78
          ca ucs-root-ca.crt <-- entfernen
          auth-user-pass
          cipher AES-128-CBC
          comp-lzo yes
          dev tun
          auth-nocache
          <ca>
          -----BEGIN CERTIFICATE-----
          223hE9DISTtt4Btvhp3ubjyApfbcvA3AhA4CsFZ5h3WX2XQgliUOcYBjr2ZyX6OS2
          bzpcT7iZ30WAZk6OBbaGtGbvyZNsvEQWWN2hgyeC9pbjYYoBL5UVIhzAn7pslN8
          xhpaPDCSvpDWpMTb5a6Kt4tGwvIidEJRWgOUagyaSoT2TeMsjg5ltnIF021VcPUUa
          2XkQVWjgJxBvneQ4V0ec2vLvhnb1DbdEtR93bfdbewkOuHHbNK85geSHsrKv8DSL
          P6J71bl00vqSTDOTnAji2RaE5aL5fkVe6dWrrzKnO8UGjThf09BXQTK4Uu2cpFz01S
          ----END CERTIFICATE----
          </ca>

Speichern Sie die Datei z.B. unter dem Namen ios.ovpn ab. Die Datei kann nun auf beliebigem Weg in die OpenVPN App übertragen und eingelesen werden und eine Verbindung hergestellt werden.

Schülerinnen und Schüler für OpenVPN Verbindung freischalten

Ein Lehrer kann sich mit diesen Einstellungen direkt Verbinden, Accounts von Schülerinnen und Schülern werden jedoch abgelehnt. Dazu muss dies in der Firewall eingestellt werden. 
Dazu auf der Firewall (10.1.0.11) per Browser anmelden. 
(Ich bitte um Entschuldigung dass meine Firewall auf deutsch umgestellt ist, ich gehe jedoch davon aus, dass sich die Schritte auch auf der englischen Variante gut übertragen lassen.)

Klicken Sie auf System --> Benutzerverwaltung --> Authentifizierungsserver

Klicken Sie auf  "+Hinzufügen". 
Tragen Sie jeweils ein (siehe auch: Bild unten):

  • Beschreibender Name: schuelerUndLehrer.paedml-linux.lokal
  • Hostnamen: server.paedml-linux.lokal
  • Transport: SSL - Encrypted
  • (Dadurch ändert sich der Port auf 636)
  • Suchumfang: Gesamte Unterstruktur
  • Base DN: DC=paedml-linux,DC=lokal
  • Authentifizierungscontainer: CN=users,OU=schule,DC=paedml-linux,DC=lokal
  • Klicken Sie auf Anonym binden um dies zu deaktivieren.
  • Bindungsanmeldedaten:
    • User DN: CN=ldapsuche,CN=Users,DC=paedml-linux,DC=lokal
    • Passwort: 
    • Das Passwort finden Sie auf dem Server in /etc/ldapsuche.secret
  • Benutzer-Namensattribut: samAccountName
  • Gruppen-Mitgliedsattribut: MemberOf
(Die Datei ist bis auf den Authentifizierungscontainer und dem Suchumfang identisch mit den Einstellungen aus "lehrer_server.paedml-linux.lokal".)

Klicken Sie nun auf Speichern.


Nun muss OpenVPN noch diese Benutzereinstellungen mitgeteilt bekommen. Klicken Sie auf
VPN --> OpenVPN. Wählen Sie bei "OpenVPN INTERNET (tun)" die Aktion "Server bearbeiten".
Wählen Sie bei "Backend für die Authentifizierung" den gerade erstellten Server "schuelerUndLehrer.paedml-linux.lokal" an und klicken Sie ganz unten auf Speichern.

Nun werden sowohl Schüler und Lehrer bei einer OpenVPN Verbindung authentifiziert.

IOS Zugriff auf Home-Verzeichnisse

Meine Schule hat mittlerweile zwei iPad Klassen mit 1:1 zuordnung. Die SuS brauchen den Zugriff auf Ihre Heimatverzeichnisse, auch von Zuhause. Um dies umzusetzen haben wir unsere OpenVPN Verbindung und die App FileExplorer eingesetzt.
Das Einrichten von OpenVPN für den iPad-Einsatz wird in einem zweiten Artikel beschrieben.

Einrichten von FileExplorer zur Verbindung der Home-Laufwerke

Zum Einsatz kommt die kostenfreie Variante von FileExplorer. Diese erlaubt die Verbindung zu EINEM Share (z.B. Schüler Home).  Hier eine kurze Anleitung zur Einrichtung. Voraussetzung: die Geräte müssen in einem WLAN mit Zugriff auf den Server (10.1.0.1) liegen. 
  •  App öffnen
  •  Oben links auf das "+" klicken
  •  Windows auswählen 
    • Anzeigename wählen, z.B. "Home" 
    • Host oder IP-Adresse 10.1.0.1 
    • Path Schüler- oder Lehrer-Login im paed Netz, z.B. max.muster 
    • Zugangsdaten: Schülerlogin und Schülerpw
  • Anschließend rechts oben Speichern
Nun kann in der App auf "Home" bzw. den Anzeigenamen geklickt werden um den Inhalt des Homeverzeichnisses zu sehen. 


Montag, 23. April 2018

Schülerordner Links erstellen

Über Freigaben --> Home-Verzeichnisse Schueler --> _klassen können Lehrer die Ordner der Schühler ihrer Klassen anklicken. Da diese jedoch Links auf Linuxbasis sind können diese nicht kopiert werden.

Um eine "Windows Kopie" des Ordners "_klassen" anzulegen habe ich ein kleines Powershell Skript geschrieben. Es erstellt die gleiche Ordnerstruktur des Ordners "_klassen", ändert jedoch die Links zu den Schülerverzeichnissen zu Windows Links. Der neue Ordner liegt laut Skript auf c:\klassen. Er könnte als Alternative mit zwei Unterstrichen als "__klassen" in den Ordner der Schülerverzeichnisse gelegt werden. Dann kann sich jeder Kollege die Links seiner Klasse auch herauskopieren. 


Hier das (einfache) Skript
$a = Get-ChildItem -Path '\\server\Home-Verzeichnisse Schueler\_klassen\'
foreach ($as in $a){
    $b = Get-ChildItem -Path ('\\server\Home-Verzeichnisse Schueler\_klassen\' + $as.Name)
    New-Item -ItemType directory -Path ("c:\klassen\"+$as.Name)
    foreach ($bs in $b){
        write-host $bs.Name
        $WshShell = New-Object -comObject WScript.Shell
        $Shortcut = $WshShell.CreateShortcut("c:\klassen\"+$as.Name+"\"+$bs.Name+".lnk")
        $Shortcut.TargetPath = ("\\server\Home-Verzeichnisse Schueler\"+$bs.Name)
        $Shortcut.Save()
    }
}
Das vorige Skript ließt den Ordner _klassen aus und erstellt eine Kopie mit Windows Links.
Es ist aber auch möglich alle Informationen direkt aus dem LDAP Verzeichnis zu lesen. Das Skript wird dadurch etwas Komplizierter, legt jedoch z.B. auch wirklich nur Klassen an, nicht auch von lehrern angelegte Gruppen.

$speicherpfad="c:\klassen\"
[ADSI]$domain = "LDAP://CN=klassen, CN=schueler,CN=groups,OU=schule, DC=paedml-linux,DC=lokal"
$klassen = $domain.Children.distinguishedName | ForEach-Object {[ADSI]"LDAP://$_"}
New-Item -force -ItemType directory -Path ($speicherpfad)
foreach($k in $klassen){
    New-Item -force -ItemType directory -Path ($speicherpfad + $k.Name.Replace("schule-", ""))
    $schueler = $k.Member | ForEach-Object {[ADSI]"LDAP://$_"}
    foreach($s in $schueler){
        if($s.memberOf[0].Contains("schueler")){
            $WshShell = New-Object -comObject WScript.Shell
            $Shortcut = $WshShell.CreateShortcut("c:\klassen\"+$k.Name.Replace("schule-", "")+"\"+$s.displayName+".lnk")
            $Shortcut.TargetPath = ("\\server\"+$s.Name)
            $Shortcut.Save()
        }
    }
}
Damit Dateien und Ordner, welche nicht(mehr) benötigt werden, entfernt werden, kann das Skript den jeweiligen Ordner Inhalt auslesen. Falls die Dateien keine Entsprechungen im LDAP finden können Sie gelöscht werden. Das Skript wird dadurch um einiges länger, der "klassen" Ordner ist dadurch aber immer aktuell und ein Abbild der LDAP Struktur, gelöschte Klassen oder Schüler-Links werden entfernt usw.
$speicherpfad="\\server\Home-Verzeichnisse Schueler\__klassen\"
Write-Host "Zielordner ist " $speicherpfad
$start = (Get-Date)
[ADSI]$domain = "LDAP://CN=klassen, CN=schueler,CN=groups,OU=schule, DC=paedml-linux,DC=lokal"
$klassen = $domain.Children.distinguishedName | ForEach-Object {[ADSI]"LDAP://$_"}
#Alle Dateien und Ordner im Speicherpfad bekommen das Attribut Entfernen=$true. Wenn der Ordner existieren muss wird es später wieder auf $false gesetzt.
$alteOrdner = Get-ChildItem -Path ($speicherpfad)
$alteOrdner | Add-Member -NotePropertyName Entfernen -NotePropertyValue $true
$maxi = $alteOrdner|measure
New-Item -force -ItemType directory -Path ($speicherpfad) | Out-Null
$zaehler=0
foreach($k in $klassen){
 
    #Fortschrittsanzeige, falls unerwünscht, mit # auskommentieren.
    $zaehler=$zaehler+1
    write-host ([math]::round(($zaehler/($klassen.Count)*100),0))"%"
 
    #Für jeden existierenden Ordner wird geprüft, ob es eine Klasse mit dem gleichen Namen gibt. Falls nicht bleibt das Attribut Entfernen auf $true.
    for ($i=0;$i -lt $maxi.Count; $i++){
        if($k.Name.Replace("schule-", "") -like $alteOrdner[$i] ){ 
            $alteOrdner[$i].Entfernen = $false 
        }
    }
    New-Item -force -ItemType directory -Path ($speicherpfad + $k.Name.Replace("schule-", "")) | Out-Null
 
    # $zwischenspeicher enthält alle Dateien des jeweiligen Klassenordners
    $zwischenspeicher = Get-ChildItem -Path ($speicherpfad + $k.Name.Replace("schule-", ""))
    # Für jede Datei wird das Attribut Entfernen auf $true gesetzt
    $zwischenspeicher  | Add-Member -NotePropertyName Entfernen -NotePropertyValue $true
 
    $schueler = $k.Member | ForEach-Object {[ADSI]"LDAP://$_"}
    foreach($s in $schueler){
        if($s.memberOf[0].Contains("schueler")){
            $WshShell = New-Object -comObject WScript.Shell
            $Shortcut = $WshShell.CreateShortcut($speicherpfad+$k.Name.Replace("schule-", "")+"\"+$s.displayName+".lnk")
            $Shortcut.TargetPath = ("\\server\Home-Verzeichnisse Schueler\"+$s.Name)
            $Shortcut.Save()
        }
        $max = $zwischenspeicher |measure
        #Falls die Datei durch LDAP bestätigt wurde wird Entfernen = $false
        for ($i=0;$i -lt $max.Count; $i++) {
                if($zwischenspeicher[$i].baseName -like $s.displayName){
                    $zwischenspeicher[$i].Entfernen = $false;
                }
        }
    }
    #Dateien mit Entfernen = $true löschen.
    #Links zu Schülerordnern, welche nichtmehr in der LDAP Gruppe der Klasse vorhanden sind.
    foreach($z in $zwischenspeicher){
        if($z.Entfernen){
            Remove-Item -Path ($z.FullName) -Recurse -force
        }
    }
}
#Ordner mit Entfernen=$true löschen.
foreach($o in $alteOrdner){
    if($o.Entfernen){
             Remove-Item -Path $o.FullName -Recurse -force
    }
}
#Leere Ordner löschen
Get-ChildItem -Path $speicherpfad -Recurse -Force | Where-Object { $_.PSIsContainer -and (Get-ChildItem -Path $_.FullName -Recurse -Force | Where-Object { !$_.PSIsContainer }) -eq $null } | Remove-Item -Force -Recurse
$Ende = (Get-Date)
write-host "Das Sript lief für" ([math]::round(($ende - $start).TotalSeconds,0))"Sekunden"

(Erinnerung aus dem Shutdown-Aktikel) 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

Montag, 16. April 2018

Opsi Statusfenster ausschalten.

Standardmäßig erscheint nach jeder Benutzeranmeldung ein kleines Opsi Fenster. In manchen Fällen schließt sich dieses erst relativ spät. Außerdem hat dieses Fenster unangenehme Eigenschaften, z.B. wenn man die Bildschirmauflösung ändert.
Hier möchte ich beschreiben, wie man das Fenster ausblenden kann.

Standardtwert setzen

Als erstes muss der Standartwert für das Ausblenden gesetzt werden.
Nach den öffnen von "Opsi-Configed Local" klicken Sie auf Server-Konfiguration.


Klicken Sie dann auf "Host-Parameter" --> "opsiclientd". Dort klicken Sie auf den Schlüssel "opsiclientd.event.user_login.active" und setzen den Wert auf "false". Bestätigen Sie dies mit einem Klick auf das rote Häkchen. Nun ist der Wert für alle neuen Clients gesetzt und wirksam.
Klicken Sie auf den linken Knopf neben der Server-Konfiguration um zur normalen Übersicht (der Client-Konfiguration) zurück zu kommen.

Werte für vorhandene Clients setzen


Wenn Sie bereits Clients ausgerollt haben müssen Sie den Wert für diese nochmals von Hand setzen.
Markieren Sie die Clients. Auf der rechten Seite finden Sie neben den Reitern "Produktkonfiguration" und "Netboot-Produkte" auch den Reiter "Host-Parameter". Klicken Sie auf "Host-Parameter" --> "opsiclientd". Dort klicken Sie auf den Schlüssel "opsiclientd.event.user_login.active" und setzen den Wert auf "false". Bestätigen Sie dies mit einem Klick auf das rote Häkchen.
Nun muss der Opsi-Client neu aufgespiet werden. Klicken Sie auf Produktkonfiguration und setzen Sie "opsi-client-agent" auf "setup".
Nach einem Neustart und der darauf folgenden neuionstallation des Opsi Agenten ist die Änderung nun wirksam.

Mittwoch, 11. April 2018

Tipparbeit sparen: Admin Anmeldung

Einer der ersten Tricks, welche mir von unserem Dienstleister (Danke Michael!!) verraten wurden habe ich so verinnerlicht, dass ich über deren Besonderheit gar nicht mehr nachdachte.

tl;dr


Login Domänenadmin
p\administrator
Login lokaler Admin
.\administrator

Erklärung


Bei der Anmeldung als Domänen Administrator muss man Windows sagen, an welcher Domäne man sich anmeldet (da es auch einen lokalen Admin gibt).

Der Benutzername ist dann:
paedml-linux\administrator
Samba juckt das aber nicht, es akzeptiert fast jede Zeichenfolge als Domänen-Admin, z.B.
p\administrator
Natürlich geht auch
ichwillnachhause\administrator
paedml-murmelbahn\administrator :) 

Umgekehrt möchte man sich manchmal auch als lokaler Administrator anmelden. Dazu muss man als Domäne die Bezeichnung des PCs eingeben.
computer16\administrator
Da der Computername zum Zeitpunkt der Anmeldung nicht angezeigt wird, kann das etwas anstrengend sein. Aber dafür hat Windows eine eigene Abkürzung eingebaut, die Unabhängig von der paedML an jedem Windows-Domänen PC funktioniert.
.\administrator
meldet sich am lokalen Gerät an, der Name dieses Gerätes wird dann freundlicherweise sogar eingeblendet

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.