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.

Keine Kommentare:

Kommentar veröffentlichen

Hinweis: Nur ein Mitglied dieses Blogs kann Kommentare posten.