## Donnerstag, 2. Mai 2019

### Powershell und Windows-Images

Zur Zeit beschäftige ich mich etwas mehr mit Windows Images. Zum einen, weil ich das OPS-VHD Produkt teste und auch schon einsetze (und noch mehr damit geplant habe, mehr dazu hoffentlich in kürze), zum anderen, weil beim opsi-local-image-wim-capture immer wieder Fehlermeldungen erscheinen, wenn das Windows etwas länger eingeschaltet war, bevor es gecaptured wurde. Diesen Problemen versuchen wir auch auf die Schliche zu kommen.
Hier ersteinmal eine Sammlung praktischer Powershell Befehle für die Images:
Ach ja, man muss die Powershell als Administrator ausführen um mit Images arbeiten zu können!

> Get-WindowsImage -ImagePath \\backup\opsi_depot_rw\opsi-local-image-win10-x64-capture\installfiles\install.wim

gibt die verschiedenen Images des Capture-Produkts aus. Der Pfad kann an die verschiedenen Capture Produkte angepasst werden.
Um eine Version von Windows aus dem Installer zu entfernen könnt ihr mit dem vorigen Befehl die Indes Nummer des Zieles herausfinden, mit

> Remove-WindowsImage -ImagePath \\backup\opsi_depot_rw\opsi-local-image-win10-x64-capture\installfiles\install.wim -Index 3 -CheckIntegrity

kann man z.B. das Image an Stelle 3 löschen.
(Dies dauert ein paar Minuten)
Wenn man aus einer Sammlung von Windows Installationsdateien ein Image aus dem Verbund exportieren wollt geht das mit:

> Export-WindowsImage -SourceImagePath \\backup\opsi_depot_rw\opsi-local-image-win10-x64-capture\installfiles\install.wim -SourceIndex 1 -DestinationImagePath \\backup\opsi_depot_rw\opsi-local-image-win10-x64-capture\installfiles\export.wim

Um das Exportierte Image (index 1) in eine andere Wim Datei zu verschieben muss das exportierte Image in einen Ordner (Hier C:\winmount) gemountet werden.

> Mount-WindowsImage -ImagePath \\backup\opsi_depot_rw\opsi-local-image-win10-x64-capture\installfiles\export.wim -ReadOnly -Index 1 -Path C:\winmount

Danach kann es zu einer anderen install.wim hinzugefügt werden.

> Add-WindowsImage -ImagePath \\backup\opsi_depot_rw\opsi-local-image-win10-x64-capture\installfiles\install.wim -CapturePath C:\winmount -Name "Name_des_neuen_Images"

## Dienstag, 12. Februar 2019

### paedML Linux 7.1

Hallo!
Das Update der paedML Linux für Version 7.1 steht in den Startlöchern. Es werden vor allem viele Detailverbesserungen enthalten sein, z.B. das Single Sign-On für die Schulkonsole.
Nach dem Update werden ein paar Tricks meines Blogs jedoch obsolet:

1. die Anmeldung als Administrator funktioniert nur noch mit paedml-linux\Administrator, die Abkürzungen. z.B. p\Administrator werden nicht mehr klappen.
2. Der im Blog beschriebene Trick mit dem Ausblenden des Opsi-Statusfensters muss rückgängig gemacht werden. Das paedml-login Opsi-Paket ersetzt viele der Login-Skripte (Icons kopieren, Informationen über Benutzer anzeigen usw. ) Diese werden von diesem Opsi-Statusfenster aufgerufen.
   Falls Sie meine Änderungen durchgeführt haben, setzen Sie für die Clients und das Depot "Host-Parameter" --> "opsiclientd" -->"opsiclientd.event.user_login.active" wieder auf "true" und rollen Sie den client-agent neu aus, wie in der Anleitung ausführlich beschrieben wurde.
3. Einige meiner Druckeranmelde Tipps und Tricks wurden im Update umgesetzt. Zur Verteilung können Sie nun zusätzlich und optional die Gruppenrichtlinie paedMLL_Druckerverbinden nutzen. Dazu müssen Sie im Ordner \\backup\opsi_depot_rw\update71 das Powershell-Skript DruckerGPO.ps1 ausführen. Dadurch wird eine Gruppenrichtlinie mit ihrer aktuellen Druckerkonfiguration erzeugt. Sobald Sie Änderungen an den Drucker-Zuordnungen durchführen, muss das Skript erneut gestartet werden, damit die Richtlinie aktualisiert wird.
   Ich empfehle für Schüler und Lehrer jeweils eine Verknüpfung "Drucker reparieren" anzulegen, welche ein gpupdate durchführt. Dadurch können die Benutzer bei Bedarf die Drucker neu installieren lassen.
   Rechtsklick auf dem Desktop --> Neu --> Verknüpfung
   Als Pfad wählen Sie C:\Windows\System32\gpupdate.exe, als Icon können Sie in der Standardbibliothek SHELL32.dll ein passendes Icon auswählen.
   Verteilen können Sie dieses Icon über aproflehrer und aprofschueler.
4. Bei der paedML-Linux 7.1 wurde die Anmeldung überarbeitet. Es kann nach dem Update jedoch passieren, dass nicht der Standard-Hintergrund geladen wird. Für diesen Fall können Sie folgenden Skript verwenden. Es löscht abweichende Hintergrundbilder aus den Ordnern der SuS:
   $sus = Get-ChildItem "\\server\Home-Verzeichnisse Schueler\"
   foreach( $s in $sus){
   if (Test-Path "\\server\Home-Verzeichnisse Schueler\$s\AppData\Microsoft\Windows\Themes\TranscodedWallpaper" ){
   if(
   ((Get-Item "\\server\Home-Verzeichnisse Schueler\$s\AppData\Microsoft\Windows\Themes\TranscodedWallpaper").Length -gt 240000) -or ((Get-Item "\\server\Home-Verzeichnisse Schueler\$s\AppData\Microsoft\Windows\Themes\TranscodedWallpaper").Length -le 220000)){
   Remove-Item –path "\\server\Home-Verzeichnisse Schueler\$s\AppData\Microsoft\Windows\Themes" –recurse
   write-host "Der Schüleraccount von $s wurde bereinigt." -ForegroundColor Green
   }
   }
   }
5. Der Samba Bug, der das Verwenden des Klassenorder _klassen verhinderte, ist mit der paedML Linux 7.1 natürlich behoben. D.h. meine Anleitung für einen Ersatz ist damit praktisch obsolet.

Weitere Änderungen werde ich hier im Artikel aufführen.

## 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.
Neuere Posts Ältere Posts Startseite
Abonnieren Posts (Atom)