Lange war es ruhig, ich weiß, dafür hatte ich letztens nen Server 2016 gehabt der per Inplace Upgrade von 2012R2 aktualisiert wurde und einfach nur total kaputt war.
Ich wurde dann ein wenig motiviert, etwas darüber zu erzählen 😉
Nicht so ungewöhnlich, dass der Servermanager fehlte und sich per Run-Befehl auch starten lies.
Richtig komisch wurde es eigentlich, dass die DHCP Konsole gefehlt hat, das war sowas wie mein erster Anlauf und der erste Thread hatte herrliche Theorien und Vorschläge zur Lösung, die aber alle nicht so wirklich ans Ziel kamen.
Ein Paar Schritte wie das ganze funktioniert schnell erklärt, weil einfach ist das nicht und hat fast zwei Tage Recherche gebraucht, damit die Kiste wieder vernünftig lief:
- DISM Fehler analysieren und reparieren
- komische Nebenfehler bekommen, die nur indirekt was damit zu tun haben
- Features wieder installieren lassen
- Administrative Tools wieder bekommen
Sieht alles sehr einfach aus, ist allerdings doch sehr zeitintensiv, darum gehen wir mal ein bisschen ins Detail.
wie gesagt, stieß ich zuerst auf diesen Thread:
https://community.spiceworks.com/t/domain-controller-gone-bonkers-server-manager-missing-dhcp-feature-missing/706072/11
Wir fangen also an mit den üblichen Verdächtigen:
Mit dem Befehl Get-WindowsFeature bekommen wir eine tolle Übersicht von installierten Features, sehen auch, dass DHCP und RSAT-Tools installiert sind.
Wir versuchen die neu zuinstallieren zuerst mit Remove-WindowsFeature RSAT-DHCP deinstallieren und mit Install-WindowsFeature RSAT-DHCP wieder installieren. Joa klappte nur nicht so ganz.
Also machen wir uns an die nächsten Befehle, man kennt sie ja:
sfc /scannow
DISM /Online /Cleanup-Image /StartComponentCleanup
DISM /Online /Cleanup-Image /RestoreHealth
Aber leider brachte das auch nichts, außer dem tollen
Error 0x800f081f - The source files could not be found
Schon am Verzweifeln, kopiert man die DHCP MMC aus den lokalen RSAT Tools auf den Server, nur um dann festzustellen, dass die MMC das Modul nicht laden kann. HÄ? Ja genau.
Also machen wir weiter und nachdem wir den 0x800f081f Fehler auch noch bekommen, nachdem wir hundert mal das Image als Source nehmen oder andere Win2016 Server, stößt man plötzlich auf ein ganz anderen Artikel:
https://blog.nuvotex.de/fixing-winsxs-manually-when-dism-resigns/
Hier wird erstmals drauf hingewiesen, dass man im CBS.log nach einem fehlerhaften Updatekatalog schauen soll, das dann im besten Fall auch runterladen.
Soweit so gut, wenn das Update das man braucht nicht leider schon nicht mehr verfügbar ist.
Dann muss man das Update komplett manuell entfernen. Dazu gibts dann durch mehrere Quellen nun ein kleines Script, dass man auf dem Server direkt ausführt
Wenn das Update bei euch verfügbar ist, dann könnt ihr euch den Schritt mit dem Script sparen und der Anleitung aus dem Link folgen.
Aus der Funktion in der ersten Lösung habe ich mir was schönes zusammengebastelt, im Prinzip löscht man erst alle Catalog- und Manifestdateien aus dem lokalen Speicher, anschließend übernimmt man die Berechtigungen der Registrykeys und löscht diese dann alle.
DISM wird das Update dann nicht mehr als fehlerhaft erkennen und seine Komponenten reparieren können.
https://stackoverflow.com/questions/24366162/set-acl-requested-registry-access-is-not-allowed/35844259
unbedingt die KB Nummer im Script anpassen!
function Enable-Privilege { param( ## The privilege to adjust. This set is taken from ## http://msdn.microsoft.com/en-us/library/bb530716(VS.85).aspx [ValidateSet( "SeAssignPrimaryTokenPrivilege", "SeAuditPrivilege", "SeBackupPrivilege", "SeChangeNotifyPrivilege", "SeCreateGlobalPrivilege", "SeCreatePagefilePrivilege", "SeCreatePermanentPrivilege", "SeCreateSymbolicLinkPrivilege", "SeCreateTokenPrivilege", "SeDebugPrivilege", "SeEnableDelegationPrivilege", "SeImpersonatePrivilege", "SeIncreaseBasePriorityPrivilege", "SeIncreaseQuotaPrivilege", "SeIncreaseWorkingSetPrivilege", "SeLoadDriverPrivilege", "SeLockMemoryPrivilege", "SeMachineAccountPrivilege", "SeManageVolumePrivilege", "SeProfileSingleProcessPrivilege", "SeRelabelPrivilege", "SeRemoteShutdownPrivilege", "SeRestorePrivilege", "SeSecurityPrivilege", "SeShutdownPrivilege", "SeSyncAgentPrivilege", "SeSystemEnvironmentPrivilege", "SeSystemProfilePrivilege", "SeSystemtimePrivilege", "SeTakeOwnershipPrivilege", "SeTcbPrivilege", "SeTimeZonePrivilege", "SeTrustedCredManAccessPrivilege", "SeUndockPrivilege", "SeUnsolicitedInputPrivilege")] $Privilege, ## The process on which to adjust the privilege. Defaults to the current process. $ProcessId = $pid, ## Switch to disable the privilege, rather than enable it. [Switch] $Disable ) ## Taken from P/Invoke.NET with minor adjustments. $definition = @' using System; using System.Runtime.InteropServices; public class AdjPriv { [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen); [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)] internal static extern bool OpenProcessToken(IntPtr h, int acc, ref IntPtr phtok); [DllImport("advapi32.dll", SetLastError = true)] internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid); [StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct TokPriv1Luid { public int Count; public long Luid; public int Attr; } internal const int SE_PRIVILEGE_ENABLED = 0x00000002; internal const int SE_PRIVILEGE_DISABLED = 0x00000000; internal const int TOKEN_QUERY = 0x00000008; internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020; public static bool EnablePrivilege(long processHandle, string privilege, bool disable) { bool retVal; TokPriv1Luid tp; IntPtr hproc = new IntPtr(processHandle); IntPtr htok = IntPtr.Zero; retVal = OpenProcessToken(hproc, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref htok); tp.Count = 1; tp.Luid = 0; if(disable) { tp.Attr = SE_PRIVILEGE_DISABLED; } else { tp.Attr = SE_PRIVILEGE_ENABLED; } retVal = LookupPrivilegeValue(null, privilege, ref tp.Luid); retVal = AdjustTokenPrivileges(htok, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero); return retVal; } } '@ $processHandle = (Get-Process -id $ProcessId).Handle $type = Add-Type $definition -PassThru $type[0]::EnablePrivilege($processHandle, $Privilege, $Disable) } Enable-Privilege SeTakeOwnershipPrivilege $kb = "KB5008207" $files = gci -file C:\Windows\servicing\Packages | where{$_.Name -like "*$kb*"} foreach($file in $files){ remove-item $file.fullname -force } $keys = @() $keys += get-childitem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\Packages" -recurse | where{$_.Name -like "*$kb*"} $keys += get-childitem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\PackageDetect" -recurse | where{$_.Name -like "*$kb*"} $keys += get-childitem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\ComponentDetect" -recurse | where{$_.Name -like "*$kb*"} $keys += get-childitem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\PackageIndex" -recurse | where{$_.Name -like "*$kb*"} $BuiltinAdmin = ([Security.Principal.SecurityIdentifier]'S-1-5-32-544').Translate([System.Security.Principal.NTAccount]) #$BuiltinAdmin = New-Object System.Security.Principal.NTAccount($admingroup) foreach($key in $keys) { $repkey = $key.name.replace("HKEY_LOCAL_MACHINE\","") $regKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey("$repkey",[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,[System.Security.AccessControl.RegistryRights]::TakeOwnership) $regACL = $regKey.GetAccessControl() $regACL.SetOwner($BuiltinAdmin) $regKey.SetAccessControl($regACL) $regRule = New-Object System.Security.AccessControl.RegistryAccessRule ($BuiltinAdmin,"FullControl","ContainerInherit,ObjectInherit","None","Allow") $regACL.SetAccessRule($regRule) $regKey.SetAccessControl($regACL) $childkey = gci $key.pspath if($childkey) { $childrepkey = $childkey.name.replace("HKEY_LOCAL_MACHINE\","") $childregKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey("$childrepkey",[Microsoft.Win32.RegistryKeyPermissionCheck]::ReadWriteSubTree,[System.Security.AccessControl.RegistryRights]::TakeOwnership) $childregACL = $childregKey.GetAccessControl() $childregACL.SetOwner($BuiltinAdmin) $childregKey.SetAccessControl($childregACL) $childregRule = New-Object System.Security.AccessControl.RegistryAccessRule ($BuiltinAdmin,"FullControl","ContainerInherit,ObjectInherit","None","Allow") $childregACL.SetAccessRule($childregRule) $childregKey.SetAccessControl($childregACL) } $hklmkey = $key.name.replace("HKEY_LOCAL_MACHINE","HKLM") cmd.exe /c "reg delete ""$hklmkey"" /f" | out-null }
Wenn man das Script nun fertig hat durchlaufen lassen hat, führt man nochmal die DISM Befehle durch und diese laufen dann auch hoffentlich erfolgreich durch (zumindest taten sie es dann bei mir).
Anschließend gibt es tatsächlich nur noch eine Sache zu tun und zwar den Servermanager neu zu installieren. Dazu führt man einfach den folgenden Befehl aus:
dism /online /enable-feature /all /featurename:server-gui-mgmt
Anschließend sollten die Administrativen Tools dann auch wieder verfügbar sein genauso wie die DHCP Konsole und andere Rollen und Features die installier sind und sich vorher nicht verwalten liesen.
Zum Abschluss noch etwas Lektüre (dies bitte nicht als Lösungsansätze nehmen, dient nur zur Veranschaulichung):
https://answers.microsoft.com/en-us/windows/forum/all/how-to-fix-mmc-could-not-create-the-snap-in-while/d97082d4-2637-429e-95ca-221745d08e66
https://serverfault.com/questions/747438/how-to-find-out-why-windows-server-feature-installation-failed
Was 1 Brain! 😀 <3