Sonntag, 18. November 2018

Windows Image

Die Windows Installation (so wie ist) auf eine neue Festplatte ziehen - geht auch mit Boardmitteln.

Achtung! Diese Anleitung enthält Befehle, die bei falscher Anwendung Daten löschen können! Bitte unbedingt zwischen Copy & Paste auch den Kopf verwenden!

Was wird benötigt?
  • Ein Startmedium mit Windows PE: USB Stick oder CD (ISO)
  • Einen genügend großen Zwischenspeicher: USB Festplatte oder Netzlaufwerk,
  • ein paar Tools von Windows und
  • gewusst wie.

Ablauf

  1. Den Computer mit Windows PE starten (Man kann Quell und Ziel Laufwerk anschließen)
  2. Die Quell Laufwerk mit dism in ein image "aufzeichnen".
  3. Optional: Umbauen und wieder mit WinPE starten.
  4. Das Ziel Laufwerk partitionieren.
  5. Das Image auf das Ziel Laufwerk "anwenden".
  6. Startsystem wieder herstellen
Es ist also ziemlich simpel, die Befehle und Schritte sind im Prinzip im zweiten Link beschrieben. Ich will dies hier aber mit etwas Erfahrungen und Tipps ausbauen.

Windows PE

Es gibt Windows PE in 32 und 64 bit Version. Obwohl WinPE mit einem einfachen CMD Fenster startet, kann man von dort die meisten (portablen) Programme und Tools einfach starten. Aber - die 64 bit Version hat kein 32 bit Subsystem, mann kann also immer nur Programme der gleichen Architektur starten!
Ich habe darauf verzichtet WinPE aufwendig anzupassen. Ich habe einfach ein paar Scripte erstellt, einen Teil habe ich auf dem USB Stick bereitgestellt und Andere einfach auf einem zentralen Netzwerklaufwerk abgelegt.
Es genügt ein kleiner USB2 Stick, WinPE ist nicht groß (ca. 300 MB ohne Anpassung, ca 450 MB mit Powershell) und die Ladezeiten sind nicht sehr lang.
An meinem System lagen die Zeiten zwischen 25 und 30 sec, der Unterschied USB2 und USB3 sowie Powershell ging quasi in der "Nachdenkzeit" des Reset und BIOS unter.

Damit man mit der Tastatur klar kommt braucht man ein Startscript.
wpeutil SetKeyboardLayout 0407:00000407
start cmd
Die Tastatur ist sonst mit englischem Layout und die Änderung mit SetKeyboardLayout wirkt erst im neuen Fenster.
Um eine Netzwerkverbindung herzustellen ist dies sinnvoll. Dieses Zeile enthält auch einen Trick:
Hat man ein Linux SMB Share, welches mit anonymen Zugriff konfiguriert ist, kann sich WinPE nicht ohne Konto verbinden (anonymer Zugriff ist auch in Windows seit Version 10 1709 abgeschaltet).
Bei WinPE hilft es einfach einen Benutzer anzugeben den es nicht gibt: net use ist zufrieden und Linux ignoriert es.
net use * \\server1\Shares /user:willi password 
net use * \\server1\Sicherung  
Natürlich kann man die paar Zeilen auch in ein CMD Script packen und auf dem USB Stick bereitstellen.
Die beiden net use Befehle erzeugen ein Laufwerk z: und y: - das Laufwerk x: ist das Systemlaufwerk von WinPE.

Image aufzeichnen

Zunächst  muss man ermitteln, welche Laufwerksbuchstaben WinPE für die eingehängten Festplatten vergeben hat. Dazu gibt es viele Möglichkeiten, ziemlich eindeutig und gezielt funktioniert es mit diskpart. Am Prompt: DISKPART> gibt man folgende Reihenfolge ein:
list disk
sel disk 0
detail disk
Der erste Befehl zeigt alle eingebauten Festplatten.
Der zweite Befehl selektiert die Quellfestplatte, die Ziffer ist also entsprechend anzupassen.
Der dritte Befehl zeigt alle Details der gewählten Disk inklusive der dort enthaltenen Volumes und Laufwerks Buchstaben.
Tipp: Auch gleich die Nummer für das Zielaufwerk merken, die wird im nächsten Abschnitt benötigt.
Ein Beispiel zum Aufzeichnen des Images von Laufwerk C
dism /capture-image /imagefile:y:\wim\backup.wim /capturedir:c: /name:C-WinOS
Man kann recht einfach ein Beispielscript im Laufwerk z: vorbereiten und mit notepad (läuft unter WinPE) anpassen und dann ausführen. So spart man sich viel Tipparbeit. Die meiste Zeit wird die Bandbreite zum Ziellaufwerk sehr gut genutzt, die Geschwindigkeit für die Aufzeichnung hängt also vom Umfang, Geschwindigkeit Quelle und Netzlaufwerk (oder USB Laufwerk) ab.

Ziel Laufwerk partitionieren

Das Ziellaufwerk wird in der Regel neu partitioniert und kann dabei auch völlig neu strukturiert werden! Beispiel:
War das Quellsystem eine MBR Partition kann die Ziel Platte ein GPT System werden (falls der PC UEFI unterstützt).
Achtung! Wird jetzt die falsche Disk verwendet, werden ohne Nachfrage Daten gelöscht!
Die Empfehlung von Microsoft (man findet Unterschiedliche) für eine GPT Partitionsstruktur aus meinem zweiten Link sieht als diskpart Script wie folgt aus:
rem == CreatePartitions-UEFI.txt ==
rem == These commands are used with DiskPart to
rem    create four partitions
rem    for a UEFI/GPT-based PC.
rem    Adjust the partition sizes to fill the drive
rem    as necessary. ==
select disk 0
clean
convert gpt
rem == 1. System partition =========================
create partition efi size=100
rem    ** NOTE: For Advanced Format 4Kn drives,
rem               change this value to size = 260 ** 
format quick fs=fat32 label="System"
assign letter="S"
rem == 2. Microsoft Reserved (MSR) partition =======
create partition msr size=16
rem == 3. Windows partition ========================
rem ==    a. Create the Windows partition ==========
create partition primary 
rem ==    b. Create space for the recovery tools ===
shrink minimum=500
rem       ** NOTE: Update this size to match the
rem                size of the recovery tools 
rem                (winre.wim)                    **
rem ==    c. Prepare the Windows partition ========= 
format quick fs=ntfs label="Windows"
assign letter="W"
rem === 4. Recovery tools partition ================
create partition primary
format quick fs=ntfs label="Recovery tools"
assign letter="R"
set id="de94bba4-06d1-4d40-a16a-bfd50179d6ac"
gpt attributes=0x8000000000000001
list volume
exit
Bevor jetzt das Script mit dieser Befehlszeile angewendet wird, ist wie im vorherigen Abschnitt bitte genau zu prüfen auf welche Disk mit welcher Nummer das Ziel geschrieben werden soll! Das Script kann man einfach mit Notepad ändern und speichern.
DiskPart /s Z:\Scripts\CreatePartitions-UEFI.txt

Image anwenden

Für den jetzigen Schritt sind eigentlich alle Vorbereitungen getroffen und man kann direkt loslegen. Ich habe hier das Script zur Anwendung mal etwas vor dem "Doppelklick" entschärft.
@if "%1" == "" goto usage 
dism /Apply-Image /ImageFile:%1 /Index:1 /ApplyDir:%2
goto end
:usage
@echo Nichts passiert: Parameter fehlen, bitte so verwenden
@echo apply y:\wim\backup.wim W:\
:end

Startsystem erzeugen

Jetzt muss man mindestens noch die neue Platte "start-fähig" machen. Dazu genügt in der Regel ein Befehl. Wieder als ungefährliches Script:
@if "%1" == "" goto usage 
bcdboot %1 /s %2
goto end
:usage
@echo Nichts passiert: Parameter fehlen, bitte so verwenden
@echo makeboot W:\Windows S:
:end
Jetzt kann man mit dem Befehl
wpeutil shutdown
das System herunterfahren, den USB Stick und die Quellplatte entfernen und das System sollte sich mit der neuen Platte einfach starten lassen.

Recovery System erzeugen

Eigentlich fehlt noch etwas, das Recovery System. Vielleicht war es auf der Quellplatte schon nicht vorhanden, aber bei einer Windows Installation gehört es dazu. Solange die Laufwerke aus Schritt 4 noch vorhanden sind, sollte dieses Script das Recovery System einrichten.
@if "%1" == "" goto usage 
md %1
copy %2\System32\Recovery\winre.wim %1\winre.wim
%2\System32\reagentc /setreimage /path %1 /target %2
goto end
:usage
@echo MakeRecovery R:\Recovery\WindowsRE W:\Windows
:end
Man kann das Recovery System auch später im laufenden System einrichten.

Windows PE bekommen

Windows PE kann man aus unterschiedlichsten Quellen verwenden und "bauen". Ich habe einfach das aktuelle Windows ADK heruntergeladen, man braucht die Deployment Tools und das Windows PE AddOn (Details im ersten Link)
Das "Kommandocenter" für die Umgebung öffnet man nach der Installation mit "Windows + Bereit (tippen)" + rechte Maustaste "Als Administrator ausführen".
Hier braucht man nur zwei Befehle, Laufwerk mit 600-800 MB freiem Platz und einen freien USB Stick. Die Laufwerksbuchstaben und Pfade müssen angepasst  werden.
Achtung, der USB Stick wird (nach Nachfrage) neu formatiert!
copype amd64 D:\WinPE_amd64
MakeWinPEMedia /UFD D:\WinPE_amd64 Q:
Man kann natürlich auch ein ISO File erstellen, z.B. für Test in Hyper-V.
MakeWinPEMedia /ISO D:\WinPE_amd64 D:\ISO\WinPE_amd64.iso

Anmerkung für Dual Boot x86 und amd64:
Ich hatte hier schon mal einen dualen Windows Setup Stick gebaut. Ob die Anleitung mit aktuellem ADK funktioniert habe ich nicht getestet.

Probleme lösen

Ich hatte unklare, nicht reproduzierbare Probleme mit der 32 bit Version von WinPE und dism /Capture-Image. Ich habe daraufhin konsequent alles mit der 64 bit Version getestet. Ob die 32 bit Version ein generelles Problem hat kann ich nicht sagen.
Abbruch von dism
Beim Prozess "Aufzeichnen" hatte ich neben zu wenig Platz auf dem Netzlaufwerk Probleme die mit spartanischen Fehlermeldungen von dism abgebrochen wurden. dism schreibt aber normal eine Logdatei nach \Windows\Logs\DISM.
War der Vorgang erfolgreich sollte man die übrigens löschen, sonst ist sie beim nächsten Mal und einer Fehlersuche unnötig groß.
Durch die Hinweise im Log konnte ich die Fälle lösen.
  • chkdsk /f half beim Fehler mit dem Volumebit auf dem Laufwerk.
  • Löschen einer Datei im User Ordner \OneDrive half, weil dism offenbar eine "kaputte" Datei erst aus der Cloud komplett synchronisieren wollte.
  • Ein kleine verdächtige Datei im Browsercache des Users wurde vom Virenscanner erkannt. Ich hatte zu dem Zeitpunkt nicht WinPE gestartet sondern das normale System laufen und die Platte zusätzlich angesteckt.
Ich war durchaus froh über diese "Fehler" die sonst mit auf die neue Platte gewandert wären.
Der Virenscanner im laufenden System wird den Aufzeichnungsprozess verlangsamen! Aber man sollte durchaus erstmal das System auf der originalen Platte gründlich scannen!

Neue Platte startet nicht
Trotz identischem Vorgehen, hatte ich ein UEFI System, welches zunächst nicht starten wollte. Nachdem ich etwas konfus probiert und untersucht habe, war die Lösung ziemlich einfach:
  • Einfach solange in den Fehler starten (3 mal) bis Windows von sich aus in die Startoptionen startet. 
  • Dort einmal die Option: "Abgesichert mit Kommando Fenster" auswählen, anmelden und dann neu starten. Windows repariert so offenbar sein Startsystem automatisch.

Recovery System funktioniert nicht
Die obigen Befehle funktionieren scheinbar alle einwandfrei, aber im fertigen System meldet der Befehl
reagentc /info
das Recovery System als nicht verfügbar.
Der Befehl zum Aktivieren bringt eine Fehlermeldung
reagentc /enable
REAGENTC.EXE: Startkonfigurationsdaten können nicht aktualisiert werden.
Nach langer erfolgloser Suche habe ich einfach das probiert:
del c:\Windows\System32\Recovery\ReAgent.xml
Danach funktionierte /enable einwandfrei.

Zum Schluss

Ich bin oft für spartanische Lösungen, mit dem was gerade da ist. Windows hat seit langem schon alles an Board um eine Systeminstallation (die Systempartition) zu sichern und auf eine neue Festplatte zu bringen. Man braucht keine "bunten" Tools, die im Zweifelsfall auch bloß Dinge machen die man nicht versteht und die Fehlerhaft sein können.
Was ich hier aufgeschrieben habe funktioniert Schritt für Schritt "zu Fuß" und wenn man will mit Batch- oder Powershellscripts. Die c't hat vor Jahren schon mal ein Script für dism & Co vorgestellt welches noch weiter geht. Es erstellt eine Sicherung, die sich mit Setup wieder installieren lässt. (WIMage)
Diese Methode hat gegenüber dem üblicherweise verwendeten sektorbasiertem 1:1 Image durchaus Vorteile:
  • Dateisystem wird quasi auf Fehler überprüft
  • Quell und Zielsystem können unterschiedlich sein:
    • BIOS / UEFI
    • Partitionsgröße - man muss z.B. nicht vorher zeitaufwendig defragmentieren und verkleinern um ein System von einer großen Partition auf eine kleiner SSD zu migrieren
    • Partitionsanordnung
  • man könnte Ordner aus dem Aufzeichnungsprozess ausschließen (WimScript.ini - [ExclusionList])
  • unterschiedliches Dateisystem - muss ich noch untersuchen?
  • Zielsystem ist anschließend erstmal defragmentiert.

Nützliche Tools für WinPE

Die Anleitung zur Integration von Powershell findet man im dritten Link. Die Startzeit vergrößert sich dadurch etwas. 
Ein aktueller OpenSource Dateimanager mit 32 und 64 bit: DoubleCommander  
Ansonsten laufen relativ viele OpenSource Programme ohne Probleme.

Wichtige Quellen

Microsoft: Boot to Windows PE
Microsoft Aufzeichnen und Anwenden von wim Images
Microsoft WinPE mit Powershell

Dienstag, 30. Oktober 2018

Dateiversionsverlauf oder?

... FileHistory - die Übersetzung von eindeutigen und meist kurzen englischen Begriffen endet oft in sehr kunstvollen Umschreibungen.

Windows Sicherung

Windows bietet dem Anwender eine Absicherung seines Systems in zwei Stufen:
Die eigene Dateien -> Mit Dateiversionsverlauf sichern - zu finden in Menü Einstellung/Sicherung.
Das System -> kann man "Wiederbeleben" durch Reset, Recovery, neues Setup.
Die beiden Dinge sind vom normalen Anwender halbwegs beherrschbar, zumindest die Einrichtung und dann braucht er sich eigentlich nicht mehr kümmern.
Der Insider kann auch noch mit dism ein Image seines gesamten Systemes ziehen.

Wiederherstellung

Allzu oft ist die Sicherung eine Einbahnstraße im Kopf: Meine Daten werden ja gesichert...
Aber was muss ich im Fehlerfall machen: Wie gehe ich mit einer Sicherung um? Wie komme ich an meine (alten) Daten?

Alte Version einer Datei wiederherstellen

Per Standard wird stündlich eine Sicherung der Benutzerdaten durchgeführt. Hat man eine Datei versehentlich geändert und sie wurde vor dieser Änderung gesichert (kann man auch manuell triggern) kann man sie einfach über rechte Maustaste und dem Kontextmenü  "Vorgängerversionen wiederherstellen" zurück holen.

Dateiversionsverlauf auf einem neuen PC / nach einer Neuinstallation wieder herstellen

Sich durch die Menüs von Windows 10 derzeit hindurchzufitzen ist echt nicht einfach: Neue Menüs und alte (Systemsteuerung) geben sich scheinbar die Hand und existieren unter unterschiedlichen Begriffen scheinbar auch parallel.

Bildlich gesprochen passiert folgendes:
  • Die Sicherung erfolgt auf einem Extra Laufwerk, anders lässt es Windows nicht zu!
    • zweite interne Festplatte
    • USB Festplatte
    • Netzlaufwerk
    • Virtuelle Platte auf einem anderen Laufwerk
  • Man hat nach einer Neuinstallation das alte Sicherungslaufwerk zur Hand und macht es im System einfach als Laufwerk verfügbar - einstecken oder wie auch immer.
    • Jetzt wird es echt spannend, aktuell in der Version Windows 10 1803 findet man den notwendigen Punkt unter Systemsteuerung\System und Sicherheit\Dateiversionsverlauf -  ein "altes" Systemsteuerungsfenster und dort dann Laufwerk auswählen. Man kommt irgendwie auch über die neuen Menüs und Sicherungseinstellung\weiter Sicherungsoptionen und dann ganz unten "Siehe erweiterte Einstellungen" dorthin. 
    • In dem Moment wo man das Laufwerk markiert, öffnet sich eine Auswahlbox mit den vorhanden Dateiversionsverläufen (mit Benutzer- und Computername)
    • Durch Auswahl des Benutzers wird das Laufwerk aktiviert.
    • Nach kurzer Zeit ist die alte Sicherung analysiert und man kann seine alten Dateien wiederherstellen (Menüpunkt links persönliche Dateien wiederherstellen).
    • Ob sich der neuen Benutzername und der Alte unterscheiden spielt keine Rolle, Windows stellt den/die alten Benutzernamen in der Sicherung fest und fragt diese ab. Der alte Benutzer- und Computername wird danach durch den Neuen ersetzt.
Ich habe versucht den aktuellen Stand in Bildern zu dokumentieren.
Will man das alte Laufwerk so als Sicherungslaufwerk beibehalten ist alles in Ordnung. Ist es nur ein temporäres Laufwerk gewesen, kann man die alten Dateien nicht nur einmalig wieder herstellen, man kann auch die Historie auf das neue Sicherungslaufwerk kopieren.

  • Im letzten Menü noch einmal auf Laufwerk auswählen klicken.
  • In dem Moment wo man ein neues Laufwerk auswählt, wird man gefragt ob man die vorhandenen Dateien ebenfalls verschieben will.
  • Ein Datei verschieben Dialog öffnet sich automatisch nach kurzer Zeit.

Umzug von Benutzerdaten

Der Umzug von Benutzerdaten bei einer Neuinstallation (z.B. alten PC von 32 bit Version auf 64 bit Umstellen) erscheint mir mit dieser Methode relativ leicht, auf alle Fälle besser als in alten Benutzerverzeichnissen mit Rechte Problemen zu kämpfen.
  • USB Laufwerk anstecken
  • Sicherung einrichten und mit dem Benutzer kontrollieren ob alle notwendigen Pfade einbezogen sind! 
  • Vorhandene Sicherung bei Bedarf auf das neue Laufwerk umstellen (wird automatisch kopiert)
  • Sicherung manuell triggern und kontrollieren, USB Laufwerk danach auswerfen.
  • PC neu installieren
  • USB Laufwerk anstecken und wie oben beschrieben die Daten wiederherstellen.

Montag, 15. Oktober 2018

Powershell und Windows Update

Der Hyper-V Server hat ein Management Interface welches aus mehreren Scripts besteht:
Auszug

  • sconfig.cmd
    • sconfig.vbs
    • WUA_SearchDownloadInstall.vbs
    • ...
Die VBS-Scripts liegen %systemroot%\system32\en-us\ (bzw. anderen Sprachen).
Gesteuert wird das Windows Update über die Windows Update API. Die Doku ist schwerer Stoff, ich habe mir von verschiedenen Seiten und aus dem oben erwähnten Script ein paar Dinge zusammen gelesen und will es hier kurz notieren.
Meine Scripts in dem Artikel sind alle nicht perfekt, sondern eher als Lösungsansatz gedacht. Damit kann man z.B. beim Hyper-V Server etwas mehr tun als nur mit der sconfig "Oberfläche".

Die Schritte etwas im Detail

Zuerst muss man festlegen welche Updates man suchen will (ein paar Details):
$Criteria = "IsInstalled=0 and Type='Software'"
$Criteria = "IsInstalled=0 and Type='Driver'"
Dann werden verschiedene Com Objecte eingerichtet und z.B. die Titel der verfügbaren Updates angezeigt.
$Searcher = New-Object -ComObject Microsoft.Update.Searcher
$SearchResult = $Searcher.Search($Criteria).Updates
# Titel anzeigen
$SearchResult|select Title
Powershell bietet an der Stelle etwas mehr Komfort zur Suche und Auswahl als die eigentliche API. Deshalb füge ich hier bewusst den Neubau der Update Collection ein. Eigentlich ist diese jetzt schon in $SearchResult enthalten.
$updatesToDownload = New-Object -ComObject Microsoft.Update.UpdateColl
$SearchResult|%{ if($_.Title -match "KB2267602") {$updatesToDownload.Add($_)}}
$updatesToDownload|select Title
Danach werden die gewünschten Updates heruntergeladen.
$Session = New-Object -ComObject Microsoft.Update.Session
$Downloader = $Session.CreateUpdateDownloader()
$Downloader.Updates = $updatesToDownload
$Downloader.Download()
Um es ganz korrekt zu machen, kann man noch eine Collection der wirklich heruntergeladenen Updates erstellen ...
$updatesToInstall = New-Object -ComObject Microsoft.Update.UpdateColl
$SearchResult|%{ if($_.isDownloaded) {$updatesToInstall.Add($_)}}
... um dann die Updates zu installieren.
$Installer = New-Object -ComObject Microsoft.Update.Installer
$Installer.Updates = $updatesToInstall
$Result = $Installer.Install()
Als Abschluss wird bei Bedarf ein Neustart ausgeführt.
If ($Result.rebootRequired) { shutdown.exe /t 0 /r }

Komplett in einem Script

Das Ganze als ein Script mit zwei Parametern am Anfang:
Den Type entweder weglassen (Alle suchen) oder auf Software oder Driver setzen.
Bei $compare kann man entweder nur einen "*" für "Alles" setzen oder wie im Beispiel den Begriff mit Wildcards für ein bestimmtes Update.
# Parameter
$Criteria = "IsInstalled=0 and Type='Software'"
$compare= "*" # "*KB2267602*"
# Suche notwendige Updates 
$Searcher = New-Object -ComObject Microsoft.Update.Searcher
$SearchResult = $Searcher.Search($Criteria).Updates
# Zusammenstellung Download Collection
$updatesToDownload = New-Object -ComObject Microsoft.Update.UpdateColl
$SearchResult|%{ if( $_.Title -like $compare ) {$updatesToDownload.Add($_)}}
#$updatesToDownload|select Title
#$updatesToDownload.Count
# Download Updates 
$Session = New-Object -ComObject Microsoft.Update.Session
$Downloader = $Session.CreateUpdateDownloader()
$Downloader.Updates = $updatesToDownload
if ($updatesToDownload.Count -gt 0) {$Downloader.Download()}
# Zusammenstellung Install Collection
$updatesToInstall = New-Object -ComObject Microsoft.Update.UpdateColl
$SearchResult|%{ if($_.isDownloaded) {$updatesToInstall.Add($_)}}
# Install Updates 
$Installer = New-Object -ComObject Microsoft.Update.Installer
$Installer.Updates = $updatesToInstall
$Result = $Installer.Install()
# Neustart wenn gefordert
If ($Result.rebootRequired) { shutdown.exe /t 0 /r }

Was lief bisher?

Man kann sich auch die gesamte Historie anzeigen lassen:
Wann wurde welches Update installiert? Die Zeiten werden in UTC angezeigt!
# Historie abfragen
$session = new-object -comobject Microsoft.Update.Session
$searcher = $session.CreateUpdateSearcher()
$history = $searcher.QueryHistory(0, $searcher.GetTotalHistoryCount())
$history |select Date,Title |more
Leider liefert das Ergebnis am Ende immer viele Leerzeilen.

Mehr Komfort

Die Powershell Gallery halt ein umfangreiches Scriptmodul "PSWindowsUpdate" bereit mit dem der Windows Update Service wohl sehr komfortabel behandelt werden kann. Ich habe mir das zunächst nur kurz angeschaut. Um das Script aus der PSGallery zuinstallieren muss man ein paar Vorbereitungen treffen, das ist hier ganz gut beschrieben.

Noch ein Special

In $SearchResult stehen auf den zweiten Blick noch mehr Informationen!
So erzeugt man eine Liste mit Detailinformationen, z.B. mit der UpdateID oder dem Download Link.
#$Criteria = “IsInstalled=0 and Type=’Software'”
$Searcher = New-Object -ComObject Microsoft.Update.Searcher
$SearchResult = $Searcher.Search($Criteria).Updates

foreach ($update in $SearchResult) {
  $title = $update.Title
  $guid = $update.Identity.UpdateId
  $title
  $guid
  $bundles = $update.BundledUpdates
     foreach($bundledUpdate in $bundles) {
        foreach($content in $bundledUpdate.DownloadContents) {
           if (!$content.IsDeltaCompressedContent) {
              $url = $content.DownloadUrl
              $url
            }
        }
    }
}

Mit der so ermittelten UpdateID wäre es auch möglich die Suche nach speziellen Paketen auszuführen:
$Criteria = "IsInstalled=0 and UpdateID='a3fafd03-b687-49e6-9df9-3963057ce376'"

Es gibt mit CIM und "PowerShell remoting session" auch einen Ansatz die VMs zentral zu patchen.

Sonntag, 16. September 2018

Virtuelle Maschinen erzeugen und administrieren mit Powershell

Kennt Ihr das? Hilfe Texte stimmen nicht, Beschreibungen funktionieren nicht? Das Thema Powershell und VM ist auch damit ganz schlimm gebeutelt.

VM anlegen

Man kann eine leere VM anlegen, das ergibt ein relativ mageres Ergebnis:
1GB RAM, Generation 1, keine Disk.
New-VM –Name 'Test1'
Will man in etwa das Ergebnis, was der Hyper-V Manager Wizard nur mit der Eingabe "Name" erzeugt, muss man es so machen:
$vm = 'Test2'
New-VM –Name $vm –NewVHDPath "$vm.vhdx" -NewVHDSizeBytes 127GB
Bisher haben wir VMs der Generation 1 erzeugt.
Viel mehr als im nächsten Befehl, kann man bei New-VM gar nicht angeben.
$vm = 'Test3'
New-VM –Name $vm -Generation 2 –MemoryStartupBytes 4GB –NewVHDPath "$vm.vhdx" -NewVHDSizeBytes 32GB
Alles weitere muss man mit Set-VM* Befehlen machen.

Startmedien - DVD Laufwerk

Das wichtigste dabei (und nicht ganz einfach wie man denkt) wäre ja die Angabe von Start- bzw. Installationsmedien. Kaum noch existieren wirklich optische Laufwerke, das Handling am Hyper-V Server wäre extrem unpraktisch. Man kann ohne weiteres ISO oder VHD Images einbinden, aber nur wenn sie lokal auf dem Hyper-V Server liegen. Microsoft hat eine sehr eigenartige Sicherheit eingebaut, wenn es um den Zugriff auf ein Image geht, welches auf einem Server liegt.
Auch die hier beschriebenen Wege funktionieren bei mir nur in der manuellen Art für VMs der Generation 1!

Mein Resumé: Startmedien gehen nur lokal

Mag sein es funktioniert in AD Umgebungen, ich habe es in einer Workgroup versucht, und wirklich viel experimentiert, ich bekomme es nicht hin! Man kann auf dem Hyper-V Host ein Image mounten und sich einen Überblick von den Laufwerksbuchstaben ausgeben lassen.
Mount-DiskImage -ImagePath \\omv1\shares\ISO\de_windows_7_All_with_sp1_x64_.iso
Get-Volume|? DriveType -eq CD-ROM

Für VMs der Generation 1 kann man nun im Hyper-V Manager das physische Laufwerk auswählen und zuweisen. (Mit Set-VMDvdDrive kann man nur echte DVD Laufwerke zuweisen)

Bei VMs der Generation 2 kann man nur mit lokalen ISO Images arbeiten!
Mann muss zunächst ein DVD Laufwerk hinzufügen.
Add-VMDvdDrive -VMName $vm -Path D:\ISO\Name.iso
Mit Set-VMDvdDrive kann man lokale ISO Images zuweisen und ändern.

Konfiguration Netzwerk, Bootreihenfolge und Prozessor

Damit der Startvorgang nicht am PXE Boot "verhungert" sollte man noch die Boot Reihenfolge festlegen
$VMDVD=Get-VMDvdDrive -VMName $vm
Set-VMFirmware -VMName $vm -FirstBootDevice $VMDVD
Oder man legt die Reihenfolge neu fest, die neue vhdx Datei kann sowieso nicht booten, er sollte in dem Fall beim ersten Mal von der DVD starten
$VMHDD=Get-VMHardDiskDrive -VMName $vm
Set-VMFirmware -VMName $vm -BootOrder $VMHDD, $VMDVD
Will man den (einzigen) Netzwerk Adapter noch mit dem (einzigen) virtuellen, externen Switch verbinden, kann man das so machen
Connect-VMNetworkAdapter -VMName $vm -SwitchName (Get-VMSwitch|? Switchtype -eq External).Name
Während wir den mindest RAM bei der Einrichtung festgelegt haben, hat die VM bisher nur einen Prozessor. Man kann so alle verfügbaren Prozessoren zuweisen
Set-VMProcessor -VMName $vm -Count (Get-VMHost).LogicalProcessorCount

Zusammenfassung

Hier ein Template/Script für eine neue VM, Der VM Name kann als Parameter übergeben werden oder wird abgefragt (Mandatory=$true).
In der ersten und zweiten Zeile werden alle Parameter gesetzt. Ich habe deshalb auf übermäßig viele Variablen verzichtet.
<#
.SYNOPSIS 
    Das Script erstellt eine neue virtuelle Maschine
.DESCRIPTION 
    Das Script erstellt eine neue virtuelle Maschine
.EXAMPLE 
    CreateVM Name 
.NOTES 
    Generation 2, 32 GB HDD, 4 GB RAM, alle Prozessoren
#>
#region Params
param(
    [Parameter(Position=0, Mandatory=$true,HelpMessage="Name der VM",ValueFromPipeline=$false)]
    [System.String]
    $vm=""
)
#endregion 

New-VM –Name $vm -Generation 2 –MemoryStartupBytes 4GB –NewVHDPath "$vm.vhdx" -NewVHDSizeBytes 32GB
Add-VMDvdDrive -VMName $vm 
$VMDVD=Get-VMDvdDrive -VMName $vm
$VMHDD=Get-VMHardDiskDrive -VMName $vm
Set-VMFirmware -VMName $vm -BootOrder $VMHDD, $VMDVD
Connect-VMNetworkAdapter -VMName $vm -SwitchName (Get-VMSwitch|? Switchtype -eq External).Name
Set-VMProcessor -VMName $vm -Count (Get-VMHost).LogicalProcessorCount

Für die DVD habe ich mal noch einen "Würgaround" gefunden.
Dieser Mehrzeiler schaut nach, ob die ISO Datei im lokalen Pfad schon existiert und kopiert diese ansonsten dahin.
$DVD = "name.iso"
$Ziel = 'D:\ISO\'
$Quelle = '\\omv1\shares\iso\'
$Diff = Compare-Object -ReferenceObject (gci -Path $Ziel) -DifferenceObject (gci -Path $Quelle)
Copy-Item -Path (($Diff|? InputObject -like $DVD).InputObject).FullName -Destination $Ziel
# DVD einlegen
Set-VMDvdDrive -VMName $vm -Path $Ziel$DVD

Damit das lokale Verzeichnis nicht vermüllt, kann man ja von Zeit zu Zeit die ISOs entfernen, die nicht mehr eingebunden sind.
$Ziel = 'D:\ISO\'
$InUse = (Get-VMDvdDrive -VMName *|? Path -like $Ziel*).Path
$Exist = (gci -Path $Ziel).Fullname
$Diff = Compare-Object -ReferenceObject $Exist -DifferenceObject $InUse
Remove-Item ($Diff|? SideIndicator -eq '<=').InputObject -confirm

Aufräumen

Dieser Einzeiler löscht alle VMs und deren virtuelle HDDs, deren Name mit Test beginnt.
Jeder Löschvorgang verlangt nach Bestätigung! Bitte genau hinschauen, ich übernehme keine Verantwortung!
Ich hoffe, dass ich in einem Jahr noch weiß, wie dieser Einzeiler funktioniert.
% ist das Kürzel für ForEach, $_ Ist das aktuelle Element der Schleife
-process enthält den Ausführungsteil(, beide Befehlsteile werden in getrennten Schleifen abgearbeitet.)
-InputObject die Eingabe
% -process {Remove-Item (Get-VMHardDiskDrive -VMName $_.name).Path -Confirm ; Remove-VM -VMName $_.name} -inputobject (Get-VM -VMName Test*)

Informationen

Man kann sich relativ schnell einen Überblick über die Cmdlets verschaffen und detaillierte Hilfe abrufen. (Leider stimmt auch in den Hilfetexten nicht alles)
Get-Help Hyper-V
Get-Help Set-VM*

Mittwoch, 5. September 2018

Storage Pool auf dem Hyper-V Server verwalten

Wie bekommt man einen gespiegeltes HDD System, welches unter Windows Server 2012 als Storage Pool eingerichtet war, wieder online?

Ein paar Befehle für die  Analyse des Storage mit Powershell 

So bekommt man ein Bild der vorhandenen Disks:
get-disk

Number Friendly Name Serial Number                    HealthStatus         OperationalStatus      Total Size Partition
                                                                                                             Style
------ ------------- -------------                    ------------         -----------------      ---------- ----------
1      Samsung SS... S1DBNSAF878015M                  Healthy              Online                  232.89 GB GPT
0      Samsung SS... S21PNSAG155972R                  Healthy              Online                  232.89 GB GPT
6      VD1           {05bf1a4e-44d2-11e5-80d3-7824... Healthy              Offline                   1.82 TB GPT
5      VD2           {dcdf1498-41bd-11e5-80cf-7824... Healthy              Offline                    930 GB GPT
7      TOSHIBA Ex...            92N5P3KXT             Healthy              Online                  931.51 GB MBR
Zwei Platten sind Offline, die Disks 2,3 und 4 "fehlen".

Die Abfrage der Virtuellen Disk ergibt folgendes Bild:
get-virtualdisk

FriendlyName ResiliencySettingName OperationalStatus HealthStatus IsManualAttach    Size
------------ --------------------- ----------------- ------------ --------------    ----
VD1          Mirror                OK                Healthy      False          1.82 TB
VD2          Simple                OK                Healthy      False           930 GB
Obwohl die eigentlichen Laufwerke im Storage Pool ausgeblendet werden, kann man sich die echten Laufwerke anzeigen lassen:
Get-physicaldisk

FriendlyName              SerialNumber    CanPool OperationalStatus HealthStatus Usage            Size
------------              ------------    ------- ----------------- ------------ -----            ----
Samsung SSD 840 EVO 250GB S1DBNSAF878015M True    OK                Healthy      Auto-Select 232.89 GB
WDC WD2003FYPS-27Y2B0     WD-WCAVY7115101 False   OK                Healthy      Auto-Select   1.82 TB
TOSHIBA External USB 3.0  92N5P3KXT       False   OK                Healthy      Auto-Select 931.51 GB
Samsung SSD 850 EVO 250GB S21PNSAG155972R False   OK                Healthy      Auto-Select 232.89 GB
ST31000333AS              9TE24JDS        False   OK                Healthy      Auto-Select  931.5 GB
WDC WD2003FYPS-27Y2B0     WD-WCAVY5334150 False   OK                Healthy      Auto-Select   1.82 TB

Get-PhysicalDisk kann aber mehr, mit Hilfe von Format-List habe ich mal eine Ausgabe der Zuordnung zu den Hardware Ports gebastelt
Get-PhysicalDisk| Select-Object -Property DeviceId,PhysicalLocation,BusType,MediaType,FriendlyName,SerialNumber| Sort-Object -Property DeviceId |Format-Table

DeviceId PhysicalLocation                BusType MediaType   FriendlyName              SerialNumber
-------- ----------------                ------- ---------   ------------              ------------
0        Integrated : Adapter 0 : Port 1 SATA    SSD         Samsung SSD 850 EVO 250GB S21PNSAG155972R
1        Integrated : Adapter 1 : Port 0 SATA    SSD         Samsung SSD 840 EVO 250GB S1DBNSAF878015M
2        Integrated : Adapter 1 : Port 1 SATA    HDD         ST31000333AS              9TE24JDS
3        Integrated : Adapter 1 : Port 4 SATA    HDD         WDC WD2003FYPS-27Y2B0     WD-WCAVY5334150
4        Integrated : Adapter 1 : Port 5 SATA    HDD         WDC WD2003FYPS-27Y2B0     WD-WCAVY7115101
6        Integrated : Adapter 0 : Port 0 USB     Unspecified TOSHIBA External USB 3.0  92N5P3KXT

Den Zusammenhang zwischen Storage Pool, VirtualDisk und PhysicalDisk kann man auch anzeigen lassen. Dazu braucht man zunächst ein Object auf den Storage Pool
$stpool = (Get-StoragePool -FriendlyName "SP1")
Get-VirtualDisk -StoragePool $stpool

FriendlyName ResiliencySettingName OperationalStatus HealthStatus IsManualAttach    Size
------------ --------------------- ----------------- ------------ --------------    ----
VD1          Mirror                Degraded          Warning      False          1.82 TB

Get-PhysicalDisk -StoragePool $stpool

FriendlyName          SerialNumber    CanPool OperationalStatus HealthStatus Usage          Size
------------          ------------    ------- ----------------- ------------ -----          ----
WDC WD2003FYPS-27Y2B0 WD-WCAVY7115101 False   OK                Healthy      Retired     1.82 TB
WDC WD2003FYPS-27Y2B0 WD-WCAVY5334150 False   OK                Healthy      Auto-Select 1.82 TB
Für die Information auf welcher Disk das Volume des Systemlaufwerkes ist, habe ich diese Befehlskette gefunden.
Get-Disk (Get-Partition | ? isboot).DiskNumber

Storage Pool Disk wieder aktivieren

Ich möchte das VD1 wieder online ist, dies wird nämlich nicht automatisch getan, wenn der Storagepool an einen andere Maschine gehangen wird. Da VD1 eine (virtuelle) Disk ist, geht das mit dem set-disk Cmdlet
Set-Disk -Number 6 -IsOffline $False
Es gab bei mir die Situation, dass eine Disk nach dem Import von einer Physical Disk in eine vhdx Datei mit dem Hyper-V Manager sowohl offline als auch readonly war. Auch readonly lässt sich mit Set-Disk beheben.
Set-Disk -Number 5 -IsReadonly $False

Storage Pool Disk Fehler behandeln

Es gibt hier eine detaillierte Beschreibung wie man Fehler im Storage Pool behandeln kann. Hinbekommen habe ich das so aber noch nicht.
Versucht man in einem StoragePool, der 2 HDD enthält und darin eine Mirror VirtualDisk - eine HDD geordnet zu entfernen und diese danach durch eine Andere zu ersetzen, funktioniert das praktisch nicht. Obwohl man eine Platte als Retired markiert, lässt sie sich nicht entfernen. Man muss erst einen neue HDD zum StoragePool hinzufügen. Aber selbst das ist mir nicht richtig gelungen.
Dies habe ich versucht:
Set-PhysicalDisk -FriendlyName "WDC WD2003FYPS-27Y2B0" -SerialNumber WD-WCAVY7115101 -Usage Retired
Repair-VirtualDisk -FriendlyName "VD1"
# Zwei Varianten um das Objekt der richtigen PhysicalDisk zu bekommen
$PDToRemove = Get-PhysicalDisk | Where-Object { $_.Usage -eq 'Retired'}
$PDToRemove = Get-PhysicalDisk -FriendlyName "WDC WD2003FYPS-27Y2B0" -SerialNumber WD-WCAVY7115101
Remove-PhysicalDisk -PhysicalDisks $PDToRemove -StoragePoolFriendlyName "SP1"
Aber diese Aktion endet mit einem Fehler:
StorageWMI 48011,Remove-PhysicalDisk

Storage Pool entfernen

Erkenntnis: Storage Pool sieht gut aus, ist mir aber so undurchsichtig! Nach der kompletten Sicherung der Daten, habe ich einfach alles entfernt:
Set-Disk -Number 6 -IsOffline $true
Remove-VirtualDisk -FriendlyName "VD1"
Remove-StoragePool -FriendlyName "SP1"
Jetzt tauchen die Platten 3 + 4 wieder normal in der Ansicht auf (get-disk) und können normal verwendet werden. Um sie völlig leer zu machen verwendet man Clear-Disk
Clear-Disk -Number 3
Clear-Disk -Number 4
Get-Disk |Where-Object PartitionStyle –Eq "RAW" |Initialize-Disk 

Um eine neue Partition anzulegen, einen bestimmten Laufwerksbuchstaben zu vergeben und es gleich zu formatieren, kann man so vorgehen.
New-Partition -DiskNumber 3 -UseMaximumSize -DriveLetter S |Format-Volume

Dienstag, 4. September 2018

Partitionen und virtuelle Disk

In Hyper-V per default eine neue virtuelle Platte (VHDX) erstellt - und dann...

  • stellt man fest, dass Platten mit dynamischer Größe mit der Zeit viel langsamer sind als virtuelle Platten mit fester Größe. 
  • Das virtuelle Platten auf einer SSD viel schneller sind als auf einer herkömmlichen HDD
  • Das es jetzt ziemlich verschwenderisch ist, 127 GB auf der knappen SSD zu belegen, um eine virtuelle Platte zu hosten, die mit weniger als der Hälfte üppig ausgestattet wäre.

Dafür ein kurzes HowTo, wie man von dieser Ausgangssituation zum gewünschten Ziel kommt. Für einen Trockentest, kann man so eine virtuelle Disk zum Test anlegen. Die verwendeten Größen sind nur als Beispiel zu sehen.

Test Datei erstellen

Die typische System Platte sieht in etwa so aus.
PartitionNumber  DriveLetter Offset                                        Size Type
---------------  ----------- ------                                        ---- ----
1                            1048576                                     450 MB Recovery
2                            472907776                                    99 MB System
3                            576716800                                    16 MB Reserved
4                C           593494016                                126.45 GB Basic
Die folgende Zeilen erstellen eine vhdx Datei mit genau dieser Struktur. Das Windows Upgrade baut manchmal noch eine Partition hinten dran.
Dummerweise gibt es die x-VHD Powershell-Cmdlets nur, wenn die Hyper-V Rolle komplett installiert ist. Alternativ kann man dafür diskpart verwenden.
$VDisk = "D:\VHD\TestBase.vhdx"
New-VHD -Path $VDisk -SizeBytes 127GB
$Disk = Mount-VHD -Path $VDisk -Passthru|Get-Disk
$Disk
$Disk|Initialize-Disk -Passthru|Remove-Partition -PartitionNumber 1
$Disk|New-Partition -Size 450MB -GptType '{de94bba4-06d1-4d40-a16a-bfd50179d6ac}'
$Disk|New-Partition -Size 99MB -GptType '{c12a7328-f81f-11d2-ba4b-00a0c93ec93b}'
$Disk|New-Partition -Size 16MB -GptType '{e3c9e316-0b5c-4db8-817d-f92df00215ae}'
$Disk|New-Partition -Size ((get-disk $Disk.Number).LargestFreeExtent-450MB) -AssignDriveLetter|Format-Volume
$Disk|New-Partition -UseMaximumSize -GptType '{de94bba4-06d1-4d40-a16a-bfd50179d6ac}'
$DriveLetter = ($Disk|Get-Partition|Get-Volume|? Driveletter -ne $null).DriveLetter
$path = ($DriveLetter+":\Test.tmp");$file = [io.file]::Create($path);$file.SetLength(10GB);$file.Close()
Dismount-VHD -Path $VDisk
Initialize-Disk erzeugt per default eine "reserved" Partition. Da ich die Original Struktur haben wollte, wird diese zunächst gelöscht und an dritter Stelle wieder erzeugt. Für diese Test vhdx ist das unerheblich, im produktiven Umfeld würde ich das nicht machen.
Die vorletzte Zeile enthält keine Pipe sondern mehrere Befehle um einfach einen große Datei in dem Volume zu erzeugen.

Minimierung der Ausgangsdatei

Die folgende Befehlsfolge reduziert die Partition in der Datei auf ein Minimum und reduziert die vhdx Datei auf ein Minimum. Zunächst mounten wir die vhdx Datei und erhalten als Ausgabe die Tabelle der Volumes.
Mount-VHD -Path $VDisk -Passthru| Get-Disk | Get-Partition | Get-Volume
Die folgende Aktion kann eine Partition auf minimale Größe bringen. Den richtigen Laufwerksbuchstaben einsetzen:
$DriveLetter = "E"
$size = Get-PartitionSupportedSize -DriveLetter $DriveLetter
Resize-Partition -DriveLetter $DriveLetter -Size $size.sizeMin
Manchmal baut Windows Update an das Ende der Systemplatte eine Wiederherstellungspartition, diese ist eigentlich unnütz und verhindert die Verkleinerung der gesamten Disk.
So kann man Partitionen entfernen. Vorsicht! Dieser Befehl löscht Daten, fragt aber vorher nach. Bitte genau lesen! 
Der zweite Befehl listet uns den Inhalt, im dritten Befehl muss die richtige PartitionNumber nn gesetzt werden.
$Disk = Get-Disk (Get-Partition -DriveLetter $DriveLetter).DiskNumber|Get-Partition
$Disk
$Disk |? PartitionNumber -eq nn|Remove-Partition
Hat man die letzte Partition verkleinert, kann man auch die gesamte Disk verkleinern:
Dismount-VHD -Path $VDisk
Resize-VHD -path $VDisk -ToMinimumSize
Zum Schluss kann man noch die Dateigröße der vhdx Datei verringern.
Optimize-VHD -path $VDisk -mode full
Jetzt befindet sich die vhdx in minimaler Größe und kann z.B. schneller kopiert werden.

In Form bringen

Anschließend soll die Datei wieder in eine produktive Form gebracht werden. Zunächst die komplette Disk auf die gewünschte Größe bringen.
Resize-VHD -path $VDisk -SizeBytes 500MB
Danach Mounten und die Struktur anschauen, das letzte Volume kann vergrößert werden.
In der zweiten Zeile den richtigen Laufwerksbuchstaben einsetzen!
Mount-VHD -Path $VDisk -Passthru| Get-Disk | Get-Partition | Get-Volume
$DriveLetter = "E"
$size = Get-PartitionSupportedSize -DriveLetter $DriveLetter
Resize-Partition -DriveLetter $DriveLetter -Size $size.sizeMax
Jetzt noch die virtuelle Disk vom dynamischen in ein festes Format wandeln. Dabei wird eine neue Datei erzeugt.
Dismount-VHD -Path $VDisk
Convert-VHD -path $VDisk –DestinationPath V:\NeuerName.vhdx –VHDType Fixed

Nützlich

Zwischen den einzelnen Schritten, kann immer mal der momentane Zustand der vhdx Datei und der Partition überprüft werden.
Get-VHD $VDisk
Get-PartitionSupportedSize -DriveLetter $DriveLetter
So ermittelt man die Werte für Disk und Partition.
Get-Disk
Get-Partition -DiskNumber 8
$size = (Get-PartitionSupportedSize -DiskNumber 8 -PartitionNumber 2)
Resize-Partition -DiskNumber 8 -PartitionNumber 2 -Size $size.SizeMax
Die x-VHD Powershell Cmdlets lassen sich etwas umständlicher durch diskpart Befehle ersetzen
create vdisk file="c:\ttt\testBase.vhdx" maximum=1024
sel vdisk file="c:\ttt\testBase.vhdx"
attach vdisk
detach vdisk
expand vdisk maximum=20000
Anbinden und auswerfen der vhdx Datei geht auch mit dem Windows Explorer.
Die Konvertierung kann man auch mit dem Hyper-V Manager durch Import der vhdx Datei bewerkstelligen.

Will man mehr als die Standardausgabe von einem Cmdlet sehen, hilft das Format-List Cmdlet. Es gibt alle Elemente in einzelnen Zeilen aus.
Get-Disk |Format-List -Property *
Dadurch erhält man z.B. die Information über Elemente nach denen man auch Filtern kann.
Get-Disk |Where-Object {$_.Bustype -Eq "USB"}|

Ist die Zielfestplatte beim Konvertieren in den Type Fixed zu klein, ist die Fehlermeldung ziemlich unspezifisch:
Convert-VHD : Fehler beim Konvertieren des virtuellen Datenträgers.
Fehler beim Konvertieren von "d:\vhd\hyper-v-2016.vhdx".
In Zeile:1 Zeichen:1
+ Convert-VHD -path $VDisk –DestinationPath V:\Hyper-V2016-C.vhdx –VHDT ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Convert-VHD], VirtualizationException
    + FullyQualifiedErrorId : OperationFailed,Microsoft.Vhd.PowerShell.Cmdlets.ConvertVhd

Montag, 27. August 2018

Hyper-V Server verwenden

Der Plan: Hyper-V Server 2016 (download) als Grundlage für einen kleinen Server verwenden und darauf u.a. einen bisherigen Windows Server 2012R als virtuelle Maschine zu betreiben. Nebenbei sollen dann eventuell noch andere kleinere System laufen.
  • Hardware Unabhängigkeit (es gibt einige Hardwaretreiber nicht als Serverversion, auch von namhaften Herstellern wie Intel) 
  • Kein Leistungsverlust
  • Headless Betrieb, komplett von Windows 10 remote administrierbar
Ich hatte dazu schon einmal einen Artikel, den will ich hiermit konkretisieren.

Installation und Einrichtung des Servers


  • Ganz klassisch mit USB Stick, Monitor und Tastatur. Standardeinstellungen, die System Partition auf der SSD soll später verkleinert werden. 
  • System english aber Spracheinstellung deutsch, Zeitzone Berlin. 
  • Beim ersten Boot vom USB Stick auf UEFI oder BIOS Modus achten!

Mit dem Programm sconfig (nach der Anmeldung im Vordergrund und jederzeit wieder mit sconfig aufrufbar) wird folgendes eingestellt:

2) Computer Name
4) Configure Remote Man. (4-3)Enabled + ...Response to Ping
7) Remote Desktop (7-e-2)     Enabled (all Clients) ...less secure

Für den Computer Namen will er neu starten, deswegen kann man den Punkt zuletzt machen und anschließen braucht man die lokale Anmeldung nicht  mehr. Es geht alles über RDP.

Die weiteren Einstellungen erfolgen in Powershell, dazu kann man einfach das cmd Fenster hinter dem sconfig Fenster in den Vordergrund holen und Powershell starten.

Remote Desktop

Funktioniert sofort nach der obigen Grundkonfiguration, aber eben nur wenn nicht die Option 1) more secure eingestellt wurde. Das liegt offenbar an einem, nach der Installation noch nicht vorhandenem Patch des CredSSP. Nach dem Windows Update des Systems funktioniert auch die Option 1).

Netzwerk und Firewall

Windows typisch befindet sich das Netzwerk zunächst in der Category Public. Damit sind aber viele Firewall Regeln schärfer und viele Zugriff nicht zugelassen. Das ist im internen Netzwerk nicht sinnvoll, also ändern auf private. Der erste Befehl liefert uns die Informationen zum Netzwerk:
Get-NetConnectionProfile


Name             : Network
InterfaceAlias   : Ethernet
InterfaceIndex   : 3
NetworkCategory  : Public
IPv4Connectivity : Internet
IPv6Connectivity : NoTraffic
Der folgende Befehl setzt den Anschluss auf Private:
Set-NetConnectionProfile -InterfaceIndex 3 -NetworkCategory Private
Den Erfolg kann man leicht mit dem vorhergehenden Befehl überprüfen.
Die IP Adresse sollte fest sein, der DHCP Server und wenn vorhanden der DNS Server sollten so konfiguriert werden, dass der die IP Adresse fest zugeordnet ist und der Computer Name als FQDN aufgelöst werden kann.

Remote Management

Dafür sind Powershell Remoting und der Credential Security Support Provider zu aktivieren.
Enable-PSRemoting
Enable-WSManCredSSP -Role server
Damit ist die Server Konfiguration abgeschlossen

Einrichtung am Client Windows 10

Das Netzwerk und die Firewall sind zunächst auch so einzurichten, dass die NetworkCategory  : Private ist (siehe oben).
Der Computer Name des Hyper-V Servers muss als FQDN auflösbar sein, bitte so prüfen.
nslookup name.domain
Funktioniert das nicht, kann/muss ein Eintrag in der Datei C:\Windows\System32\drivers\etc\hosts gemacht werden.

Hyper-V-Verwaltungstools

Man kann sich anzeigen lassen, welche Hyper-V Features schon installiert sind
Get-WindowsOptionalFeature -Online -FeatureName *hyper-v*  | where state -eq enabled| select DisplayName, FeatureName
Will man nur die Management Tools installieren, geht prinzipiell mit Powershell, ist jedoch noch nichts von Hyper-V installiert kommt leider ein Fehler. Es funktioniert nur im Dialog so wie gewünscht.
Windows Features aktivieren oder deaktivieren (nicht Apps & Features und nicht optionale Features)
Eine Krücke mit Powershell wäre: Alles installieren und die Hyper-V-Plattform wieder deinstallieren.
Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-Management-PowerShell -all
disable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V
Allerdings ist dafür wegen der Installation von Hyper-V-Plattform ein Neustart fällig.

Vertrauen in einer Workgroup 

Eigentlich ist die Empfehlung von Microsoft auf dem Client als Erstes ebenfalls den Befehl Enable-PSRemoting auszuführen. Allerdings aktiviert das primär auch die Remote Verwaltung des Clients.
Es geht auch etwas sparsamer:

$server = "name.domain"
$user = "administrator"
Start-Service -Name winrm
Set-Item WSMan:\localhost\Client\TrustedHosts -Value $server
Stop-Service -Name winrm
cmdkey /add:$server /user:$user /pass

Jetzt öffnet man den Hyper-V Manager und fügt unter dem Punkt "Verbindung mit Server herstellen" den Server mit dem Namen "name.domain" (also exakt so wie bisher verwendet) ohne Angabe eines Benutzers hinzu.

Danke, dass ich dafür diesen Artikel lesen durfte.

Remote Shutdown für den Server einrichten

Die Konfiguration wird auf dem Hyper-V Server in der Powershellkonsole durchgeführt.

Benutzer anlegen

Das Passwort wird abgefragt.
New-LocalUser "UserShutdown"

Will man verhindern, dass das Passwort altert:
Set-LocalUser -Name "UserShutdown" -PasswordNeverExpires 1

Benutzerrechte festlegen

Dazu muss zunächst ein PS Script Modul heruntergeladen und eingebunden werden.
$url = "https://gallery.technet.microsoft.com/scriptcenter/Grant-Revoke-Query-user-26e259b0/file/198800/1/UserRights.psm1"
$output = "UserRights.psm1"
# $start_time = Get-Date
Invoke-WebRequest -Uri $url -OutFile $output
# Write-Output "Time taken: $((Get-Date).Subtract($start_time).Seconds) second(s)"
Import-Module .\UserRights.psm1

User Rechte erteilen 
Grant-UserRight -Account "UserShutdown" -Right SeRemoteShutdownPrivilege"

Firewall

Regeln für "File and Print" aktivieren
netsh advfirewall firewall set rule group=”File and Printer Sharing” new enable=Yes

Volume verkleinern

Ich würde gern noch das Volume C auf dem Hyper-V Server minimieren. 48 GB sollten dafür ausreichen, derzeit ist das Volume 127 GB groß. Die Aufgabe erledigt man einfach mit:
diskpart

Am Prompt von diskpart lässt man sich die Plattenkonfiguration zeigen, wählt die gewünschte Partition und verkleinert sie um den Betrag 80000 MB. (1024 MB = 1 GB)
list disk
sel disk 0
list part
sel part 4
shrink desired=80000
exit

Probleme

Scheinbar ziemlich aktuell taucht ein Problem auf, wenn die CredSSP der beiden Maschinen einen unterschiedlichen Patchlevel haben. Ich konnte mich zwar mit dem Hyper-V Manager zu meinem Hyper-V Server verbinden und kann den komplett administrieren, eine Verbindung zu den Virtuellen Gastmaschinen schlug aber wieder mit der Meldung "CredSSP ... Oracle ... Remediation ... linkid=866660" fehl. Abhilfe schafft in dem Fall auf die Schnelle:
gpedit und diese Einstellung am Client
Quelle

Dies und das

Firewall aus und einschalten
NetSh Advfirewall set allprofiles state off
NetSh Advfirewall set allprofiles state on


Firewall Rules abfragen
Get-NetFirewallRule -name *

Donnerstag, 23. August 2018

FritzBox mit TR064 abfragen

TR064 oder TR-064 ist ein Schnittstellen Protokoll für DSL Router. Hier findet man nähere Information und ein paar Dokumente.
Das Modul FRITZBOX von FHEM kennt zwei Kommandos für TR064
get FritzBoxName tr064ServiceList
gibt einen strukturierte List mit Funktionen und Kommandos.
get FritzBoxName tr064Command Kommando
lässt TR064 Kommandos an die Fritzbox senden und man bekommt etwas zurück. Aber wie kommt man zu dem Syntax für "Kommando"?
Hier habe ich im Forum mal einen Beispielbefehl gefunden, von dem habe ich mich versucht weiter zu hangeln.
get FB7490 tr064Command WANPPPConnection:1 wanpppconn1 GetInfo
Gibt eine Liste zurück aus der man Werte zu DSL Verbindung auslesen könnte.
  • Aber was gibt es denn noch so? 
  • Wie kommt man von der Service Liste zum Befehl?
Beispiel aus der Service Liste
 Spec: http://192.168.90.1:49000/wancommonifconfigSCPD.xml    Version: 1.0
 Service: WANCommonInterfaceConfig:1     Control: wancommonifconfig1
----------------------------------------------------------------------------------------------------------------------------------
  GetCommonLinkProperties ( ) = ( NewWANAccessType NewLayer1UpstreamMaxBitRate NewLayer1DownstreamMaxBitRate NewPhysicalLinkStatus )
  GetTotalBytesSent ( ) = ( NewTotalBytesSent )
  GetTotalBytesReceived ( ) = ( NewTotalBytesReceived )
  GetTotalPacketsSent ( ) = ( NewTotalPacketsSent )
  GetTotalPacketsReceived ( ) = ( NewTotalPacketsReceived )
  X_AVM-DE_SetWANAccessType ( NewAccessType )
  X_AVM-DE_GetOnlineMonitor ( NewSyncGroupIndex ) = ( NewTotalNumberSyncGroups NewSyncGroupName NewSyncGroupMode Newmax_ds
                                    Newmax_us Newds_current_bps Newmc_current_bps Newus_current_bps Newprio_realtime_bps
                                    Newprio_high_bps Newprio_default_bps Newprio_low_bps )
Für ein funktionierendes tr064Command braucht man:
  1. Aus der Überschrift: den Wert hinter Service: 
  2. den Wert hinter Control: 
  3. Die Werte für Action stehen in der Tabelle links,
  4. dahinter steht in der Klammer das Argument 
und auf der rechten Seite (hinter dem Gleichheitszeichen) stehen die zu erwartenden Werte.

Das Kommando wird im Prinzip so zusammen gesetzt:
get FritzboxName tr064Command Service Control Action Argument Value
Beispiel 1: ohne Argument (weil die Klammer leer ist)
get FB7490 tr064Command WANCommonInterfaceConfig:1 wancommonifconfig1 GetCommonLinkProperties
Beispiel 2: Argument und Value
get FB7490 tr064Command WANCommonInterfaceConfig:1 wancommonifconfig1 X_AVM-DE_GetOnlineMonitor NewSyncGroupIndex 0

Während man ja bis zum Argument noch über die Service Liste kommt, wird es dann beim Value irgendwie schwierig. Zumindest gab es in dem Dokument  dann Anhaltspunkte, das habe ich gefunden über Google mit dem Begriff aus der Überschrift des Service Abschnittes "wancommonifconfigSCPD"

Ansonsten hilft probieren, suchen und raten.

Mittwoch, 22. August 2018

QR Code für WiFi Zugang

Ohne viel zu tippen und eventuell falsche Zahlen und Buchstaben kommt man mit modernen Android und IOS Systemen per Scan ins Wlan. WiFi Zugang (WLAN) teilen heisst der Menüpunkt im Smartphone wenn man mit dem Wlan verbunden ist. Und das Smartphone zeigt in dem Moment genau einen QR Code, den man mit dem anderen Smartphone scannen kann und mit einem Klick kann man sich verbinden.
Also einfach einen QR Code fürs Wlan basteln!

Es gibt dazu einen standardisierten String der bereitgestellt werden muss. Die einzelnen Textelemente sind durch ; getrennt. Die "" können normalerweise entfallen.
WIFI:T:WPA;P:"password";S:"SSID Name";;
Unter debian Linux
sudo apt-get install qrencode
Unter OpenWrt gibt es auch das Paket qrencode allerdings mit Einschränkungen.

Diese Befehlszeile erzeugt die PNG Datei:
qrencode -t png -o ~/SSID_wifi_config.png 'WIFI:T:WPA;P:"password";S:"SSID Name";;'


Oder unter OpenWrt erzeugt man eine SVG Datei, da ist kein PNG verfügbar!
qrencode -t svg -o ~/SSID_wifi_config.svg 'WIFI:T:WPA;P:"password";S:"SSID Name";;'
Die fertige Datei drucken, oder anderweitig zur Verfügung stellen.

Mit dem "QR Code Reader - Ohne Werbung" von Sustainable App-Developer hatte ich guten Erfolg unter Android. Er will nur Bilder aufnehmen und braucht keine weiteren Rechte.

Freitag, 17. August 2018

Mit OpenWrt Presence Informationen ermitteln

Der zentrale Router weiß ja eigentlich am Besten was los ist im Netzwerk. Ich habe mal die Idee verfolgt auf dem OpenWrt einen Prozess zu starten, der aktuelle Information von Netzwerkgeräten nach "aussen" übermittelt. Als erstes Beispiel will ich mal einen bestimmten Wlan Client ermitteln.
Da die finale kurze Abfrage Schleife leicht unübersichtlich ist, erkläre ich erstmal die einzelnen Komponenten.

Ein paar Grundlagen

Je nach Wlan Hardware hat man unterschiedliche Tools um das Wlan abzufragen. Bei mir war iw und iwinfo vorhanden. Ich muss noch prüfen ob der Befehl ip auch (und ganz universell) zu gebrauchen wäre.
Die Ausgabe wird dann für die weitere Verarbeitung gefiltert. Diese beiden Befehle liefern das gleiche Ergebnis:
iwinfo |grep -oE "wlan\d-\d|wlan\d"
iw dev | grep Interface | cut -f 2 -s -d" "

Eine Liste der im Router definierten Wlan Netzwerke.
wlan0
wlan1
wlan1-1
wlan1-2
Eine List der angemeldeten Clients bekommt man mit einem dieser beiden Befehle:
iwinfo wlan1-1 assoclist
iw dev wlan1-1 station dump
In einer Schleife über alle Wlan Netzwerke und einem Ausgabe Filter bekommt man eine komplette Liste der MAC Adressen der aktiven Wlan Clients:
for w in $(iwinfo |grep -oE "wlan\d-\d|wlan\d"); do
   iwinfo $w assoclist | grep -o -E '([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2}'
done

Abfrage einer bestimmten MAC Adresse

Jetzt alles zusammengefasst mit einer Abfrage eine bestimmten MAC Adresse und als Resultat die Übergabe an einen Dummy in FHEM (API WEB ohne csrf Token!).
Das Script /root/loop.sh
#Die Verzögerungszeit der Abfrageschleife kann auch übergeben werden
WATCHDOG_SLEEP_SEC=${1:-2}
#Url zu FHEM
u=http://192.168.56.80:8088/fhem?cmd=set%20WL_Mi6%20
MAC_ADDRESS_1="80:AD:xx:xx:xx:xx"

while sleep $WATCHDOG_SLEEP_SEC; do
 if (
      for m in $(
                 for w in $(iwinfo |grep -oE "wlan\d-\d|wlan\d")
                 do
                   iwinfo $w assoclist | grep -o -E '([[:xdigit:]]{1,2}:){5}[[:xdigit:]]{1,2}'
                 done
                )
      do
        [ "$m" = "$MAC_ADDRESS_1" ] && exit 0
      done
      exit 1
    )
 then
   c=on
 else
   c=off
 fi

 # tue nur etwas wenn sich c geändert hat
 if [ "$c1" != "$c" ] ; then wget -qs $u$c ; c1=$c ; fi

done

Automatischer Start

Jetzt fehlt noch der automatische Start in OpenWrt. Dafür ist procd zuständig. Ich habe dazu ein init Script mit minimaler Funktion erstellt (siehe auch Erklärung unter dem Text):
cat <<EOF > /etc/init.d/loop
#!/bin/sh /etc/rc.common

USE_PROCD=1

START=99
STOP=01

start_service() {
    procd_open_instance
    procd_set_param command /bin/sh "/root/loop.sh"
    procd_set_param stdout 1
    procd_set_param stderr 1
    procd_close_instance
}
EOF 
Achtung! ich lerne ja selbst immer wieder dazu und habe diesen Code mal bewusst als Here Doc  dargestellt. Wenn man den Text, inklusive der ersten und letzten Zeile, einfach in die Eingabe des Terminals wirft, wird automatisch die Datei am richten Ort erzeugt, ohne das man einen Editor bemühen muss! 
Wer das nicht will, der kopiert den Text ohne die erste und letzte Zeile in den Editor seiner Wahl.

Das Ganze muss noch ausführbar gemacht und aktiviert werden:
chmod +x /etc/init.d/loop
/etc/init.d/loop enable
/etc/init.d/loop start 
Mit dem Befehl service kann man überprüfen ob der Dienst angekommen ist und ihn auch steuern.
root@wrt1900:~# service
service "" not found, the following services are available:
boot              dropbear          linksys_recovery  network           sysfixtime        uhttpd
cron              firewall          lm-sensors        odhcpd            sysntpd           umount
dnsmasq           gpio_switch       log               rpcd              system            urandom_seed
done              led               loop              sysctl            ucitrack


War es das schon?

Es gibt natürlich weitere Ideen:

  • Umstieg auf Eventbasierte Abarbeitung ähnlich wie im verlinkten Beispiel.
  • Konfigurierbare Abfrage mehrerer Clients
  • Auslesen aller Host und Übergabe an FHEM 
Aber jetzt will ich erstmal diesen Prozess eine Weile Probe laufen lassen und ein Monitoring des Routers einrichten.

Mittwoch, 8. August 2018

Lokale Namen OpenWrt

Mit der Fritzbox war es immer etwas mystisch: Wann klappt die Namens Auflösung von lokalen Geräten? Am Ende war nur noch Konfiguration mit IP Adressen anstatt Namen angesagt. Blöd wenn man mal etwas umstellen will. Mit OpenWrt sollte es doch jetzt alles klar beherrschbar sein!?

Erkenntnisse

Scheinbar werden von dnsmasq die lokalen Namen erst geliefert, wenn die DHCP Clients die Lease aktualisiert haben. Theoretisch überleben die Leases auch einen Neustart des Routers. Ich hatte aber oft die Situation, da war nach dem Start alles erstmal weg. Die Leasetime steht per Standard auf 12 h. Da dauert es dann ganz schön lange, wenn ein Client mal die Lease erneuern will. Irgendwo hatte ich mal gelesen, dass passiert nach der halben Leasetime (6 h). Dafür muss eine Lösung her.
Frage: Wo packt man jetzt Hostnamen hin, damit sie sofort zur Verfügung stehen?
Antwort: /etc/hosts

Code

Die static Leases sind schlecht lesbar wenn sie nur die IP und Mac enthalten, der Hostname sollte da schon mit in die Liste. Damit man nicht alles doppelt halten und konfigurieren muss, wäre es jetzt sinnvoll die static Leases einfach auszulesen und in die hosts Datei zu schreiben.
Teile des Codes habe ich aus dem Ubuntu Wiki.
Die Abschnitte im Code sind entsprechend kommentiert.

#!/bin/ash

# Perform work in temporary files
temphosts1=`mktemp`
temphosts2=`mktemp`
dom=$(uci get dhcp.@dnsmasq[0].domain)

# If this is our first run, save a copy of the system's original hosts file and set to read-only for safety
if [ ! -f ~/hosts-system ]
then
 echo "Saving copy of system's original hosts file..."
 cp /etc/hosts ~/hosts-system
 chmod 444 ~/hosts-system
fi

# Read Hostnames from DHCP
for i in $(uci show dhcp | grep -oE "host\[\d+\].ip"|grep -oE '\d+'); do
  ip=$(uci get dhcp.@host[$i].ip)
  name=$(uci get dhcp.@host[$i].name)
   if [ ! -z $name ]
   then
    echo $ip $name.$dom $name >>$temphosts2
   fi
done

echo -e "\n# Ad hosts from dhcp "`date` | cat ~/hosts-system - $temphosts2 > $temphosts1

cp $temphosts1 /etc/hosts

# Clean up temp files and remind user to copy new file
echo "Cleaning up..."
rm $temphosts1 $temphosts2

# The File must be readable for everyone
chmod 644 /etc/hosts

#Restart Service
/etc/init.d/dnsmasq restart
echo "Done."


Dienstag, 31. Juli 2018

Import static Leases in OpenWrt

OpenWrt kann mit speziellen Kommandos (uci) in der Shell konfiguriert werden.
Ich habe mir immer gewünscht, statische DHCP Reservierungen einfach durch Textdateien zu konfigurieren. OpenWrt kann zwar wenigsten in der Weboberfläche die MAC Adressen per Copy&Paste einfügen, aber ich hätte es gern komfortabler.
Wer spielen will, der macht zunächst nichts kaputt. Die Konfigurationsänderungen werden zunächst nur temporär ausgeführt.
Die dhcp Konfiguration anzeigen:
uci show dhcp
uci show dhcp | grep '@host\[0\]'
uci get dhcp.@host[0].ip
Die Host Einträge in der /etc/config/dhcp stehen dort am Ende und sind nicht indiziert. Das uci indiziert sie "fließend" für uns und einen direkten Zugriff:

  • [0] ist der erste, 
  • [-1] der letzte Eintrag.

Vorbereitung

Die Standard Shell in OpenWrt ist ash. Diese kann leider nicht mit Arrays umgehen. Dazu braucht man die bash.
Um Dateien anzulegen und zu editieren arbeite ich lieber mit nano. Für die weitere Arbeit mit dem Artikel bitte die zwei Pakete installieren:

  • bash
  • nano

Hosts hinzufügen

Will man einen neuen Host eintragen geht das mit folgendem Beispiel. Ein neuer Eintrag wird angefügt und ist damit der Letzte, dieser wird um die Werte ip und mac ergänzt:
uci add dhcp host
uci set dhcp.@host[-1].ip='192.168.76.11'
uci set dhcp.@host[-1].mac='5c:af:3c:45:ad:be'
Mit einer Schleife kann man Zeilenweise den stdin lesen und und pro Zeile einen neuen Hosteintrag erstellen. Damit die static Leases lesbar bleiben sollte ein Hostname mit in die Tabelle.
Read Host per Line -> rhpl.sh
while read line; do
   array=($line)
   uci add dhcp host
   uci set dhcp.@host[-1].ip=${array[0]}
   uci set dhcp.@host[-1].mac=${array[1]}
   uci set dhcp.@host[-1].name=${array[2]}
done
Dazu eine Datei mit Wertepaaren -> wp.txt
192.168.1.1 aa:bb:cc:dd:ee:d1 host1
192.168.1.2 aa:bb:cc:dd:ee:d2 host2
192.168.1.3 aa:bb:cc:dd:ee:d3 host3
Das Ganze mit der Pipe verknüpft und fertig ist der Import!
bash rhpl.sh <wp.txt
Mit show kann man sich den Erfolg anzeigen lassen. Noch ist nichts aktiv!

Hosts löschen

Will man vor dem Import aufräumen, kann man mit einem (nicht perfekten) Einzeiler erst einmal alle Hosts löschen:
while [ $? -eq 0 ]; do uci delete dhcp.@host[-1];done
Die Schleife läuft einmal zu viel, deswegen gibt es einen Fehler. Aber alle Hosts sind gelöscht.

Host auslesen

Damit man die Liste der Werte Paar nicht komplette per Hand erstellen muss, kann man existierende Daten auslesen. Die entstandenen Dateien kann man bei Bedarf bearbeiten und anschließend importieren.

Static Leases auslesen

Um alle Hosts in eine Datei zu exportieren, braucht man eine formatierte Ausgabe
printf "$(uci get dhcp.@host[-1].ip) $(uci get dhcp.@host[-1].mac) $(uci get dhcp.@host[-1].name)\n"
Und ein Array mit den vorhandenen Indizes. Ein doppelter "Eierkuchen" liefert uns dies aus dem show Befehl.
echo $(uci show dhcp | grep -oE "host\[\d+\].ip"|grep -oE '\d+')
Eine simple Schleife liefert uns die Wertepaare als Ausgabe.
wrlp.sh
for i in $(uci show dhcp | grep -oE "host\[\d+\].ip"|grep -oE '\d+'); do
  printf "$(uci get dhcp.@host[$i].ip) $(uci get dhcp.@host[$i].mac) $(uci get dhcp.@host[$i].name)\n"
done
Die schreibt man einfach in eine Datei.
bash wrlp.sh >wpo.txt

Aktuelle Leases auslesen

Um einfach mal alle existierenden Leases auszulesen genügt ein Einzeiler (vorher bash starten!)
cut -d* -f1 /tmp/dhcp.leases |while read line; do array=($line);echo ${array[2]} ${array[1]} ${array[3]};done >wpl.txt
Die Datei dhcp.leases enthält für unbekannte Felder den "*". Der Befehl cut entfernt den Rest der Zeile ab dem störenden *, der würde sonst die Dateinamen im aktuellen Verzeichnis auflösen!

Finale

Das uci System arbeitet temporär, will man seine Änderungen fest machen, muss man diese speichern und das jeweilige System(dhcp) neu starten.
uci commit dhcp
/etc/init.d/dnsmasq restart
luci-reload

Die uci Schnittstelle ist auch über JSON erreichbar.
wiki.teltonika.lt

Ersatz für ssh-copy-id

Manchmal kommt es vor, das auf einem System das Tool ssh-copy-id nicht vorhanden ist. Dann gibt es zwar einfache Alternativen, aber wenn man nicht genau weiß worauf es ankommt, wird es schnell kompliziert. Ich mache hier raus mal einen kurzen Artikel, weil ich auch ein paar Besonderheiten der Shell gelernt habe.

Die einzelnen Schritte

Was macht ssh-copy-id bzw. was muss man berücksichtigen?
Alles in allem geht es darum, den Public Key von System 1 (lokal) nach System 2 (remote) zu übertragen um sich zukünftig vom System 1 am System 2 vereinfachten anzumelden und Befehle auszuführen.
  • lokalen Public Key an das Remotesystem senden.
  • im Remotesystem schauen ob der Pfad .ssh schon existiert, 
    • wenn nicht: Pfad anlegen.
  • in diesem Pfad schauen ob schon eine Datei .ssh/id_rsa.pub existiert, 
    • wenn nicht: mit dem Public Key erzeugen.
    • wenn schon vorhanden: prüfen ob der public Key schon enthalten ist,
      • wenn nicht: Den Public Key hinzufügen.
Wer Hemmungen hat den Programmcode zu verwenden, kann die oben genannten Schritte auch manuell mit ls und nano (Public Key einfach mit copy&paste übertragen) abarbeiten.
 Schon die erste Aufgabe ist nicht trivial, hier wird mit der Pipe gearbeitet und in Abhängigkeit der Shell stehen dort unterschiedliche Subsysteme zur Verfügung. Variablen können nicht einfach transportiert werden.
Hinweise:

  • Ich habe die Codestücke zur Erklärung ohne Maskierung geschrieben. So funktionieren sie aus der Windows cmd Konsole bzw. direkt in der Shell. Wie weiter unten erläutert muss $ und " maskiert werden!
  • der letzte Befehl (cat bzw. echo) ist nur zum Test ob es funktioniert hat.
Am Einfachsten geht es in eine Datei.

cat .ssh/id_rsa.pub | ssh user@host "cat >kee.tmp;cat kee.tmp"
In Windows sind für Linux oft schädliche Zeilumbrüche (CR/LF) enthalten, dies Variante filtert die CR aus.
type .ssh\id_rsa.pub | ssh user@host "cat | tr -d '\r' >kee.tmp;cat kee.tmp"
Nach einigen Versuchen glaube ich mittlerweile, dass es mit diesem Konstrukt auch zuverlässig in eine Variable ($pub) kommt:
cat .ssh/id_rsa.pub | ssh user@host "pub=$(cat );echo $pub"
type .ssh\id_rsa.pub | ssh user@host "pub=$(cat | tr -d '\r');echo $pub"
Ob das Verzeichnis existiert ist schnell getestet, das ODER führt den Befehl danach nur bei false aus. Vorher wird mit dem umask Befehl die richtige Berechtigung für den neuen Pfad sichergestellt.
umask 077;test -d .ssh || mkdir .ssh
Auf ähnliche Art wird die Datei getestet und bei false einfach erzeugt:
test -f .ssh/authorized_keys || cat kee.tmp >.ssh/authorized_keys
test -f .ssh/authorized_keys || echo $pub >.ssh/authorized_keys
Danach wird der Inhalt der Datei mit grep auf Vorhandensein des Key geprüft und bei false wird der public Key angehängt. Bei der ersten Variante wird die tmp datei gelöscht.
Da Leerzeichen im Public Key sind, braucht grep den Suchstring in "ein zwei drei"!
grep -q "$(cat kee.tmp)" .ssh/authorized_keys  || cat kee.tmp >>.ssh/authorized_keys;rm kee.tmp
grep -q "$(echo $pub)" .ssh/authorized_keys  || echo $pub >>.ssh/authorized_keys

Die Einzeiler

Da die Dateinamen im Code öfters gebraucht werden, definiere ich sie am Anfang als Variable. Jetzt steht noch zusätzlich die Aufgabe den Einzeiler so zu schreiben, dass alle Sonderzeichen von der Shell richtig behandelt werden. Startet man den Befehl aus der Linux Shell muss mit dem Backslash \ maskiert werden:
cat .ssh/id_rsa.pub | ssh user@host "p='.ssh';akey=\$p'/authorized_keys'; okey='kee.tmp'; cat >\$okey; umask 077; test -d \$p || mkdir \$p; test -f \$akey || cat \$okey >\$akey; grep -q \"\$(cat \$okey)\" \$akey  || cat \$okey >>\$akey;rm \$okey"
cat .ssh/id_rsa.pub | ssh user@host "p='.ssh';akey=\$p'/authorized_keys'; pub=\$(cat ); umask 077; test -d \$p || mkdir \$p; test -f \$akey || echo \$pub >\$akey; grep -q \"\$(echo \$pub)\" \$akey || echo \$pub >>\$akey"

Startet man man den Einzeiler aus der cmd von Windows, müssen lediglich die " verdoppelt werden:
type .ssh\id_rsa.pub | ssh user@host "p='.ssh';akey=$p'/authorized_keys'; pub=$(cat | tr -d '\r'); umask 077; test -d $p || mkdir $p; test -f $akey || echo $pub >$akey; grep -q ""$(echo $pub)"" $akey || echo $pub >>$akey"
Baut man den Befehl in Powershell zusammen muss mit dem Backtick ` maskiert werden.

Der Pfad .ssh gilt für die meistens Standard Systeme mit Benutzeranmeldung. Bei vielen Spezialsystemen (Zielsystem) muss der Pfad angepasst werden!
z.B. /etc/dropbear bei OpenWrt.
cat .ssh/id_rsa.pub | ssh user@host "p='/etc/dropbear';akey=\$p'/authorized_keys'; pub=\$(cat ); umask 077; test -d \$p || mkdir \$p; test -f \$akey || echo \$pub >\$akey; grep -q \"\$(echo \$pub)\" \$akey || echo \$pub >>\$akey"

Maskierung von Sonderzeichen
Windows: Nur " muss verdoppelt werden -> ""
Linux: $ und " müssen mit \ maskiert werden -> \$ \"
Powershell: $ und " müssen mit ` maskiert werden -> `$ `"

Mittwoch, 18. Juli 2018

Kalender in FHEM einbinden

Die älter Bereitstellung des Link habe ich bereits hier gezeigt. In der neuen Form des Google Kalenders gibt es den grünen iCal Button nicht mehr, der Link steht jetzt unter Einstellungen/<Kalendername>/Kalendereinstellungen und dort weit unten in der Box:
Privatadresse im iCal Format
Darunter steht der wichtige Hinweis:
Mit dieser Adresse können Sie von anderen Anwendungen aus auf den Kalender zugreifen, ohne ihn öffentlich zu machen.

Das Kalender Modul in FHEM ist aktuell überarbeitet und die Funktionen sind erweitert und geändert worden. Mittlerweile weiß ich, dass es keine gute Lösung ist ein Modul einzusetzen, welches auf einem anderen Modul aufsetzt aber völlig getrennt von dem entwickelt wird. Man schafft unnötig Abhängigkeiten.
Bei der Einbindung eines Kalenders in FHEM sollte man sich über den praktischen eigenen Umgang mit dem Kalender und dem Aktualisierungsintervall (Standard 1 h) Gedanken machen. Ein Abfallkalender der sich das ganze Jahr praktisch nicht ändert muss entweder nie oder höchsten 1 mal Tag aktualisiert werden.

1. Beispiel: 

Signalisierung der Abfalltonne, Aktualisierung einmal am Tag
define AbfallKalender Calendar ical url https://... 86400
Dann brauchen wir ein Gerät wo die aktuelle Tonne drin steht, im einfachsten Fall ein Dummy
define Tonne dummy
Und ein Timer der einmal am Tag den Kalender ausliest und das Ergebnis ablegt.
define a_Tonne at *12:00:00 set Tonne {(my $evt=fhem('get AbfallKalender events format:custom="$S" limit:from=1d,to=1d');;;;$evt?$evt:"0")}
Die Funktion:
  • Am Mittag den Kalender für morgen auslesen -> limit:from=1d,to=1d
  • Nur den Textinhalt des Eintrages lesen -> format:custom="$S"
  • $evt?$evt:"0" -> Falls kein Termin gefunden wird, wird der dann leere String durch eine 0 ersetzt.
  • Den resultierenden Text in den Dummy Tonne schreiben.
Damit kann man am Vortag des Termines ein Aktion auslösen: z.B. Nachmittag den Hinweis geben: die Tonne muss morgen raus. Und am Morgen des Termines kann man nochmal den Hinweis geben: die Tonne muss heute raus. Der Zeitpunkt im Timer entscheidet über das Auftauchen und Verschwinden des Inhaltes von der "Tonne".
Hinweis:
Bei Perl Code innerhalb von einem at Kommando müssen die ; verdoppelt werden, im define müssen sie auch verdoppelt werden, deshalb ;;;; !
Bei Perl Code im set Befehl muss eine zusätzliche Klammer () stehen, sonst wird der Perlausdruck als String gesehen: set Tonne {3+5} ergibt {3+5} im Dummy, set Tonne {(3+5)} ergibt 8 im Dummy.

2. Beispiel: 

Im Kalender stehen FHEM Device Namen (z.B. Aktoren), die Terminzeiten sind die Schaltzeiten.
Der Kalender mit stündlicher Aktualisierung:
define TestKalender Calendar ical url https://... 
Dann wiederum ein Dummy:
define Urlaub dummy
Und ein notify, welches exakt zu Beginn (start) und Ende (end) des Termines den Dummy auf on oder off setzt. (Code für die Raw Def)
define n_TestKalender notify TestKalender:changed:.* {\
  my $cmd ='on';;\
  $cmd = 'off' if ($EVTPART2 eq 'end');;\
  my $actor = fhem('get '.$NAME.' events format:custom="$S" filter:uid=="'.$EVTPART1.'" limit:count=1');;\
  fhem("set $actor $cmd");;\
}

Funktion
Das Calendar Modul wirft zum Zeitpunkt des Termines ein paar Events, davon spricht das notify genau auf diese Beiden an:
2018-07-16 16:25:00 Calendar TestKalender changed: 123456googlecom start
...
2018-07-16 16:30:00 Calendar TestKalender changed: 123456googlecom end
Der Event hat drei Teile:
  • $EVTPART0 ist uninteressant, 
  • $EVTPART1 enthält die ID des Eintrages und 
  • $EVTPART2 "start" bzw. "end".

Mit Hilfe der ID wird der Kalendertext gelesen und mit den umgewandelten "on" (start) und "off" (end) Befehlen das Device in FHEM gesetzt.
Warum sieht der Befehl so kompliziert aus?

  • Der uid/format String muss im String die doppelten Anführungszeichen enthalten! Damit dies funktioniert, muss der gesamte Befehl in ' ' gesetzt werden. Innerhalb werden dann zwar " akzeptiert aber keine Variablen mehr aufgelöst. Diese muss man hier mit Verkettung/concatenation einbauen.

Weiter unten habe ich eine (für den Anfänger) besser lesbare Variante eingebaut.

Komplexen Code auslagern

Für komplexeren Code sollte man alles in die 99_myUtils auslagern und im notify lediglich die wichtigen Parameter übergeben:
define n_TestKalender notify TestKalender:changed:.* {KalenderSub($EVTPART1,$EVTPART2,$NAME)} 
Das folgende Beispiel filtert das Ereignis (Devicenamen) "Urlaub" aus dem Kalender und setzt das gleichlautendes Device bei Start und Ende auf ja /nein. Alle anderen Events werden zwar getriggert aber verworfen.
Die sub kann man beliebig komplex gestaltet. Damit der get Befehl lesbar bleibt, habe ich alles in extra Variablen gepackt. (Code für die 99_myUtils.pm)
sub KalenderSub ($$$)
   {
     my ($uid,$cmd,$cname) = @_;
     #Logging der Paramter bei Bedarf;     #Log 1, "uid: $uid | Start/End: $cmd | cal: $cname";
     my $format = '"$S"';
     $uid='"'.$uid.'"';
     $cmd = 'ja' if ($cmd eq 'start');
     $cmd = 'nein' if ($cmd eq 'end');
     $dev = 'Urlaub';
     my $actor = fhem("get $cname events format:custom=$format filter:uid==$uid limit:count=1");
     # Unterschiedliche Abfragen auf ist exakt oder enthält möglich;
     #fhem("set $actor $cmd") if ($actor eq $dev);
     if ($actor =~ /$dev/) {fhem("set $dev $cmd")};
}

Tipp

Der Calendar Aufruf wird mit Level 3 geloggt, wenn man das nicht möchte hängt man an den Befehlsaufruf einfach noch ",1" an.
fhem("Befehl",1)

Weitere Infos:
Forum
Wiki (derzeit nicht aktuell)