Powershell Script zur Verwaltung von Computerkonten in der lokalen “Terminal Server Computers” Gruppe

Hallo, hier ist mal wieder Andy.
Bei unserer täglichen Support Arbeit sind wir auf ein Thema gestoßen, das im Artikel 2260754
“License Diagnosis tool returns error "License server <computer name> cannot issue RDS CALs to the Remote Desktop Session Host Server because the 'License server security group' Group Policy setting is enabled." , https://support.microsoft.com/kb/2260754,
beschrieben ist. Auf dem Windows 2008 Terminal Server Lizenz Server müssen demnach die Terminal Server in die lokale Gruppe „Terminal Server Computers“ eingetragen werden.

Das ist in kleineren Umgebungen kein Problem, was macht man aber in großen Terminal Server Umgebungen, mit mehreren hundert Terminal Servern und vielen Lizenz Servern?

Lösung 1: Man definiert einen Mitarbeiter mit lokalen Administrator Berechtigungen auf den Terminal Server Lizenzserver. Der Mitarbeiter überprüft, ob alle Terminal Server in die lokale Gruppe eingetragen sind. Das grenzt schon schwer an Strafarbeit, ist aber wohl gängige Praxis.

Lösung 2: Um das gesamte Thema daher etwas zu vereinfachen sind wir auf die Idee gekommen, alle Terminal Server in eine AD Gruppe aufzunehmen. D.h. ich kann in einer Gruppe alle Terminal Server verwalten. Leider unterstützt der Terminal Server Lizenz Server keine verschachtelten Gruppen. Also habe ich ein Powershell Script erstellt, dass die Computer Konten aus der AD Gruppe, in unserem Beispiel "MyTerminalServers", in die lokale "Terminal Server Computers" Gruppe einträgt. Wir verwenden diese AD Gruppe also nur zum Sammeln der TS Computer Konten. Das Script führt man dann als Administrator auf dem Terminal Server License Server aus, wobei man natürlich zumindest Leseberechtigungen im AD benötigt.

#In die Variable tSServerGroup den Namen der AD Gruppe eintragen
$tSServerGroup = "MyTerminalServers"
#Die Verbindung zur Gruppe herstellen
$tSServerQuery = '(&(objectClass=group)(sAMAccountName=' + $tSServerGroup + '))'
$Searcher = New-Object DirectoryServices.DirectorySearcher
$Searcher.SearchRoot = 'LDAP://' + $RootDSE.Properties.defaultNamingContext
$Searcher.Filter = $tSServerQuery
$Searcher.SearchScope = 'Subtree'
$tSGroup = $Searcher.FindOne()
$group
= [ADSI]("WinNT://localhost/Terminal Server Computers,group")
#Add each member of the terminal server group to the local license group
foreach ( $dNTServer in $tSGroup.Properties.member)
{
$dETserver = New-Object DirectoryServices.DirectoryEntry
$dETserver.Path = "LDAP://"+ $DNTServer
$dDEtsDomain = new-Object DirectoryServices.DirectoryEntry
$tSDomain = [regex]::match($dNTserver,"((dc)|(DC)=\w+,?)+").Value
$dETsdomain = New-Object DirectoryServices.DirectoryEntry
$dETsdomain.Path = "LDAP://"+$tsDomain
trap [Exception]
{
$group.Add("WinNT://" + $dETsdomain.dc + "/" + $dETServer.SamAccountName)
continue
}
}

Damit ist zwar die Arbeit für den Administrator erleichtert, aber er muss das Script immer noch auf jedem Terminal Server Lizenz Server ausgeführt werden. Also haben wir nach einem Weg gesucht, um auch hier die Arbeit für den Administrator zu erleichtern.

Lösung 3: Die Terminal Server Lizenz Server sind im Active Directory in der Site Konfiguration hinterlegt. Daher haben ich das Script erweitert in dem wir in jeder Active Directory Site nach dem Objekt der Terminal Server Lizenz Server suchen. Nach dem wir jetzt wissen welches Sites wir haben, ob und welche Terminal Server Lizenz Server in der Site vorhanden sind, kann über den ADSI Provider die Mitglieder der Domain Gruppe der lokalen Gruppe hinzugefügt werden. Somit beschränkt sich die Arbeit des Administrators auf das Hinzufügen von neuen Terminal Server in die Active Directory Gruppe  "MyTerminalServers" und Ausführen des Scripts.

$tSServerGroup = "MyTerminalServers"
$RootDSE = New-Object DirectoryServices.DirectoryEntry 'LDAP://RootDSE'
#Die Verbindung zur Gruppe herstellen
$tSServerQuery = '(&(objectClass=group)(sAMAccountName=' + $tSServerGroup + '))'
$Searcher = New-Object DirectoryServices.DirectorySearcher
$Searcher.SearchRoot = 'LDAP://' + $RootDSE.Properties.defaultNamingContext
$Searcher.Filter = $tSServerQuery
$Searcher.SearchScope = 'Subtree'
$tSGroup = $Searcher.FindOne()
#Die Terminal Server in der Domain suchen
$LDAPQueryLicServer = '(objectclass=licensingSiteSettings)'
$Root.Path = 'LDAP://' + $RootDSE.Properties.configurationNamingContext
$Searcher = New-Object DirectoryServices.DirectorySearcher
$Searcher.SearchRoot = $Root
$Searcher.Filter = $LDAPQueryLicServer
$Searcher.SearchScope = 'Subtree'
#Jetzt jede Site im Forest absuchen
foreach ( $licServersinSite in $Searcher.FindAll())
{
#jeden Terminal Server License Server in der aktuellen Site bearbeiten
foreach ( $oLicSrv in $licServersinSite.properties.siteserver)
{
$LicSrv = New-Object DirectoryServices.DirectoryEntry
$LicSrv.Path = 'LDAP://' + $oLicSrv
write-host "Working on lic server " + $licSrv.properties.dnsHostName
$group = [ADSI]("WinNT://" + $licSrv.properties.dnsHostName + "/Terminal Server Computers,group")
#Add each member of the terminal server group to the local license group
foreach ($dNTServer in $tSGroup.Properties.member)
{
$dETserver = New-Object DirectoryServices.DirectoryEntry
$dETserver.Path = "LDAP://"+$DNTServer
$dDEtsDomain = new-Object DirectoryServices.DirectoryEntry
$tSDomain = [regex]::match($dNTserver,"((dc)|(DC)=\w+,?)+").Value
$dETsdomain = New-Object DirectoryServices.DirectoryEntry
$dETsdomain.Path = "LDAP://"+$tsDomain
trap [Exception]
{
$group.Add("WinNT://" + $dETsdomain.dc + "/" + $dETServer.SamAccountName)
continue
}
}
}
}

Das Script übernimmt keine Aufräumarbeiten d.h. aus dem AD entfernte Computer Konten werden nicht aus der lokalen Gruppe entfernt. Dies wäre sicherlich noch eine sinnvolle Erweiterung, die ich bei Gelegenheit noch implementiere.

Nichts Neues - Wie bei allen Beispiel-Scripts übernehmen wir keine Verantwortung oder Support. Bei Bedarf kann aber gerne eine Advisory Anfrage an Microsoft gestellt werden. Und bitte vor dem produktiven Einsatz solche Scripts immer ausgiebig testen!

Ich hoffe, liebe Administratoren, dieses Beispiel erleichtert euch ein wenig euren stressigen Job!

Andy, aus dem Domain Support