Archiv für die Kategorie » Server «

21 | 06 | 2017

PowerShell: Ein schneller Versuch, ADMX zu parsen

Geschrieben von um 19:45 Uhr

Hier ein bißchen Code, um alle Einstellungen aus ADMXen inklusive Registry-Pfad und -Typ zu exportieren. Ein paar Typen könnten noch fehlen …

Achtung! In Zeilen 10 und 11 frißt der Syntax Highlighter den Typ (quadratische Klammer auf)xml(quadratische Klammer zu) unmittelbar vor der runden Klammer. Noch weiß ich nicht, wie ich ihn überredet bekomme, das anzuzeigen. Bis dahin hier der Quelltext zum Download: admx_parser

$language = "de-DE"
$root = "C:\Windows\PolicyDefinitions"
$output = @()

$admx_files = Get-ChildItem $root -Filter "*.admx"
foreach ($admx in $admx_files) {
    $admx_file = Split-Path $admx -Leaf
    $adml = "C:\Windows\PolicyDefinitions\$($language)\$($admx_file.TrimEnd('x'))l"
    if (Test-Path $adml) {
        $admx_data = (Get-Content $admx.FullName)
        $adml_data = (Get-Content $adml -Encoding UTF8)
        $adml_strings = $adml_data.policyDefinitionResources.resources.stringTable.GetEnumerator()
        $policies = $admx_data.policyDefinitions.policies
        foreach ($pol in $policies.policy) {
            $dn_string = $pol.displayName.Substring(9, ($pol.displayName.Length - 10))
            $et_string = $pol.explainText.Substring(9, ($pol.explainText.Length - 10))
            $policy_name = ($adml_strings.Where({$_.id -eq $dn_string})).'#text'
            $adml_strings.Reset()
            $policy_desc = ($adml_strings.Where({$_.id -eq $et_string})).'#text'
            $adml_strings.Reset()
            if ($pol.elements.HasChildNodes) {
                $els = $pol.elements.GetEnumerator()
                foreach ($el in $els) {
                    $reg_value = "$($pol.Key)\$($el.valueName)"
                    switch ($el.Name) {
                        'boolean' { $reg_type = 'REG_DWORD (1)' }
                        'decimal' { $reg_type = 'REG_DWORD' }
                        'text' {
                            if ($el.expandable) {
                                $reg_type = 'REG_EXPAND_SZ'
                            } else {
                                $reg_type = 'REG_SZ'
                            }
                        }
                        'enum' {
                            $ex = $el.FirstChild.FirstChild.FirstChild.Name
                            switch ($ex) {
                                'decimal' { $reg_type = 'REG_DWORD' }
                                'text' {
                                    if ($el.expandable) {
                                        $reg_type = 'REG_EXPAND_SZ'
                                    } else {
                                        $reg_type = 'REG_SZ'
                                    }
                                }
                            }
                        }
                        'list' {
                            $reg_type = 'REG_SZ (list)'
                            $reg_value = $el.Key
                        }
                        default { 
                            Write-Host $el.Name -ForegroundColor Cyan
                            $reg_type = $el.Name
                            $reg_value = $el.Key
                        }
                    }
                    $out_item = New-Object PSObject -Property @{'RegPath' = $reg_value; 'RegType' = $reg_type; 'PolicyTitle' = $policy_name; 'PolicyDescription' = $policy_desc; 'ADMXFile' = $admx_file}
                    $output += $out_item
                }
            } else {
                $reg_value = "$($pol.Key)\$($pol.valueName)"
                $reg_type = 'REG_DWORD (1)'
                $out_item = New-Object PSObject -Property @{'RegPath' = $reg_value; 'RegType' = $reg_type; 'PolicyTitle' = $policy_name; 'PolicyDescription' = $policy_desc; 'ADMXFile' = $admx_file }
                $output += $out_item
            }
        }
    } else {
        Write-Host "ADML in $language not found: $adml" -ForegroundColor Yellow
    }
}
$output | Export-CSV c:\temp\admx.csv -Encoding UTF8

Enjoy!

Tags » , , «

2

17 | 03 | 2017

„Verbindung verweigert“ zwischen SEP sesam und XenServer: Fake News

Geschrieben von um 8:09 Uhr

Wieder was gelernt: Wenn der Versuch, einen XenServer-Sicherungsclient zur SEP sesam-Topologie hinzuzufügen, mit der Fehlermeldung

E002-HOSTS   Kein Zugang auf Rechner <XENPOOLMASTER>: 2017-03-16 16:41:49: sxs-1500: Error:    Could not login to XEN Server: <class ’socket.error‘>: [[Errno 10061] Es konnte keine Verbindung hergestellt werden, da der Zielcomputer die Verbindung verweigerte]

quittiert wird, muss es noch lange nicht heißen, dass man etwas falsch gemacht hat oder dass ein Netzwerkproblem vorliegt. Einfach ignorieren und versuchen, einen Sicherungsauftrag anzulegen – die Chancen stehen gut, dass es klappen wird. Sehr gut sogar.

Es geht nämlich bei der Verbindungsprüfung nicht ein einziges Paket Richtung XenServer – ich habe es sogar mit WireShark überwacht, so sehr hat diese Meldung uns aus der Spur gebracht.

Aber so ist es halt mit allen Fake News…

Tags » , , , «

+

04 | 03 | 2017

PowerShell als Malware-Engine war gestern, jetzt ist Group Policy dran…

Geschrieben von um 20:22 Uhr

Interessante und extrem beunruhigende Beobachtung (leider schon im Feld) von Darren Mar-Elia: https://sdmsoftware.com/group-policy-blog/security-related/group-policy-malware-delivery-system/

Aber letztendlich gilt auch hier, was wir schon immer gepredigt haben: Nicht darauf verlassen, dass der Hersteller es besser weiß, sondern den Zugriff nach Möglichkeit explizit steuern.

Tags » , , «

+

14 | 01 | 2017

Der netdom-Befehl und die Sprachverwirrung

Geschrieben von um 11:19 Uhr

In einem aktuellen Projekt schon wieder erlebt und, da man es bis zum nächsten Mal garantiert wieder vergisst, hier zur Erinnerung:

Auf einem deutschen Domain Controller heißt es nicht etwa so wie in der Dokumentation

netdom trust DOMAIN1 /domain:DOMAIN2 /EnableSidHistory:YES

, sondern vielmehr

netdom trust DOMAIN1 /domain:DOMAIN2 /EnableSidHistory:JA

Also, Eselsbrücke: Bei AD-Migrationen gilt das Törtchen-Motto: „Sag‘ JA zu YES“. Happy migrating!

Tags » «

+

24 | 10 | 2016

Exchange 2013 CU14 und 2016 CU3: Probleme mit der Suche

Geschrieben von um 23:42 Uhr

Ein Wort der Warnung: Die vor kurzem erschienenen CUs von Exchange 2013 und 2016 haben anscheinend gravierende Probleme bei der Indizierung.

Ein Beispiel hier: https://social.technet.microsoft.com/Forums/en-US/a05ea439-8d7a-4d83-a8c4-fcc7e78904bf/exchnage-2013-cu-14-database-indexing-failed?forum=exchangesvradmin

In Kürze: Der Indexdienst verweigert für einige (in manchen Fällen auch für alle) Datenbanken die Indizierung. Die einzige Lösung ist die Installation eines zusätzlichen Servers mit einem älteren Patch-Stand und Verschiebung der Postfächer dorthin.

Wer also gerade sein Update plant, sollte lieber etwas warten und v2 oder halt ein nächstes CU installieren.

Tags » , , , , , «

+

09 | 10 | 2016

PowerShell Hack: Mit RDExSessionInfo RDP-Daten geliefert bekommen

Geschrieben von um 0:01 Uhr

Eigentlich ist es eher ein C#-Hack, aber wir wollen ja nicht zu pingelig sein 😉

Die Schwierigkeit, in PowerShell sinnvolle Daten über RDP-Sitzungen zu bekommen, war mir schon sehr lange ein Dorn im Auge. Ich habe mich aber die ganze Zeit davor gedrückt, die Windows-API anzuzapfen und die Infos von dort zu ziehen, aber nachdem Gernot Meyer in diesem TechNet-Thread) schon mal einen Proof Of Concept vorgenommen hat, konnte ich nun nicht mehr an mich halten und habe ein PowerShell-Modul namens RDExSessionInfo erstellt, das einige wichtige Basisdaten liefert:

  • Username, Domäne, Sitzungs-ID und -Status
  • Zeiten für Logon, Connect, Desconnect und Last Input (als DateTime-Objekt)
  • Upstream und Downstream Bytes
  • Client Build, Name und IP

Das ganze habe ich auf der PSGallery veröffentlicht, viel Spaß damit! Eine Status-Seite und kleine Einführung findet man hier im Blog.

Tags » , , , , «

+

07 | 10 | 2016

MAP in einer Workgroup-Umgebung

Geschrieben von um 22:30 Uhr

Heute mal etwas leichtere Kost, eher als Reminder gedacht.

Das Microsoft Assesment & Planning Toolkit ist ja ein bewährtes Mittel zur systemübergreifenden Performance-Analyse. Zum Beispiel, um eine Konsolidierung von Servern von Physik und „wilder Virtualisierung“ auf geordnete virtuelle Plattformen oder in die Cloud zu modellieren. So auch heute. Die Herausforderung jedoch war, dass nur einer der zu betrachtenden Server tatsächlich Domänen-Mitglied ist – und zwar in einer Domäne, in die der MAP-Server natürlich nicht aufgenommen werden durfte! Alle anderen Server gehören keiner Domäne an, und die meisten von ihnen haben nur den Default-„Administrator“ an Konten eingerichtet. Das Kennwort von diesem Administrator ist zwar nicht überall unterschiedlich, aber auch nicht überall gleich – ca. die Hälfte der Server hatte „Kennwort1“, und der Rest teilte sich in etwa gleichmäßig in „Kennwort2“ und „Kennwort3“.

Landet man in einer solchen Situation, so muss man folgendes berücksichtigen:

  • „.\Administrator“ kann MAP nicht erfassen, nur „Administrator“ oder „<COMPUTER>\Administrator“
  • Jedes Account kann in der exakten Schreibweise nur einmal vorkommen, das Erfassen von „Administrator“ mit unteschiedlichen Passwörtern ist also nicht ohne weiteres möglich
  • Im Inventory-Teil kann man jedem Eintrag, wenn man ihn manuell eingibt, oder jeder Liste, wenn man sie aus Dateien einliest, ein eigenes Konto zuweisen.
  • Im Performance-Sammlungs-Teil gibt es diese Funktion aber nicht – da kann man NUR eine Liste von Accounts zum Durchprobieren erfassen!
  • Die Schreibweise „<COMPUTER>\Administrator“ wird im Performance-Sammlungs-Teil nur für den Computer akzeptiert, der auch tatsächlich da steht. Somit mussten wir die Durchprobier-Liste in Form „Administrator – Kennwort1″,“DomainUser – DomainKennwort“, „SERVER2A\Administrator – Kennwort2“, „SERVER2B\Administrator – Kennwort2″,…“SERVER2Z\Administrator – Kennwort2″,“SERVER3A\Administrator – Kennwort3“,… erfassen. Alles andere hat nicht funktioniert.
  • Im Inventory-Teil (WMI) wird hingegen auch ein „fremder“ Computername angenommen, wenn nur der Benutzername und das Kennwort passen.

Last but not least: Die eingegebenen Accounts werden bei Abmeldung „vergessen“ und stehen beim nächsten Aufruf von MAP nicht zur Auswahl.

In einer Domänen-Umgebung wäre das alles überhaupt kein Problem…

 

Tags » , , «

+

17 | 08 | 2016

PowerShell Hack: Schnelle Übersicht über Updates gewinnen

Geschrieben von um 12:01 Uhr

Wenn wir „einfach nur“ wissen wollen, wann auf einem Haufen Maschinen zuletzt Updates installiert wurden und wie viele noch jeweils ausstehen, kann man sich wie folgt behelfen:

$servers = @('SERVER01'
,'SERVER02'
,'SERVER03'
# usw.
)
$script_block = $ExecutionContext.InvokeCommand.NewScriptBlock(@'
$wu_session = New-Object -COM "Microsoft.Update.Session";
$wu_searcher = $wu_session.CreateUpdateSearcher();
$last_instd = ($wu_searcher.QueryHistory(0,1)).Item(0).Date;
$wures = $wu_searcher.Search("IsInstalled=0 and Type='Software'");
$updates_to_install = $wures.Updates.Count;
"$($env:COMPUTERNAME) | $last_instd | $updates_to_install"
'@)
Invoke-Command -ComputerName $servers -ScriptBlock $script_block

Die Reihenfolge der zurückgelieferten Ergebnisse läßt sich hier aber nicht vorhersagen. Möchte man die Ergebnisse genau in der Reihenfolge sehen, wie die Server eingegeben wurden, müsste man den Aufruf einzeln auslösen. Die letzte Zeile ist dann durch

foreach ($server in $servers) {
    Invoke-Command -ComputerName $server -ScriptBlock $script_block
}

zu ersetzen.

Die Auswahl der zu untersuchenden Computer läßt sich freilich auch automatisieren. Um z.B. Überblick über alle Computer in einer OU zu gewinnen, kann man Anfang die Definition des Arrays durch

$servers = (Get-ADComputer -Filter * -SearchBase "OU=Computer,OU=Firma,DC=meine,DC=domain,DC=de" -SearchScope Subtree).Name

ersetzt werden.

Tags » , , , «

+

23 | 04 | 2016

PSConf.EU 2016: Das war schön

Geschrieben von um 13:30 Uhr

Ich hatte das Privileg, vom 20. bis 22. April Teilnehmer der von Dr. Tobias Weltner  ausgerichteten European PowerShell Conference in Hannover zu sein. Mit vielen hochkarätigen Speakern wie Jeffrey Snover, Bruce Payette, Jeff Wouters, Staffan Gustafsson, Richard Siddaway u. v. a., einem regen Austausch zwischen den 200 Delegierten aus vielen – nicht nur europäischen – Ländern und natürlich einem netten Abendprogram im Zoo Hannover musste das Event ein Erfolg werden. Und es hat nicht enttäuscht.

Ich freue mich auf das nächste Mal. Vielen Dank an Tobias und das Orga-Team!

Tags » «

+

16 | 01 | 2016

Exchange: Kein Drag & Drop in Outlook – Ursache und Lösung

Geschrieben von um 0:00 Uhr

Nach einer Migration von Exchange 2010 auf Exchange 2013, die mit dem DELL Migration Manager for Exchange durchgeführt wurde, haben sich Nutzer beschwert, dass sie in Outlook keine Elemente per Drag & Drop in einen bestimmten Unterordner des Posteingangs und dessen Unterordner verschieben können. Da dieser Ordner der e-Mail-Archivierung im Unternehmen diente, betraf es alle Nutzer und war auch beliebig reproduzierbar. Eine weitere Untersuchung ergab, dass:

  • auch keine neuen Elemente in diesen Ordnern erstellt werden können,
  • das Verschieben per Rechtsklick –> Verschieben nach… funktioniert
  • in OWA auch Drag & Drop funktioniert
  • nur diese archivierungsbezogenen Ordner betroffen sind
  • es keine Unterschiede in den Zugriffsrechten zwischen funktionierenden und nicht funktionierenden Ordnern gibt.

Der Blick auf die MAPI-Properties der Ordner per MFCMAPI ergab folgendes Bild, das auch schnell zur Lösung führte:

  • in Exchange 2010 fehlt in den betroffenen Ordner die Property PR_CONTAINER_CLASS komplett
  • in Exchange 2013 haben die „defekten“ Ordner zwar diese Property, sie ist aber leer
  • in beiden Systemen haben die Default-Ordner und solche, die von Hand angelegt wurden (und wo Drag & Drop ausnahmslos funktioniert) die Property auf den korrekten Wert „IPF.Note“ gesetzt.

Der Versuch, manuell den Wert für PR_CONTAINER_CLASS auf IPF.Note in einem der „defekten“ Ordner zu setzen, brachte sofort die Linderung für den betreffenden Ordner. Da 1.200 Postfächer betroffen waren und die Unterordner-Strukturen überall unterschiedlich waren, habe ich das folgende Skript geschrieben, das die Angelegenheit global heilt:


$ex_ps_url = "http://EXCHANGE01/PowerShell/"
$ex_ews_path = “C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll”
$log_file = "C:\temp\folder_class.log"
$res_file = "C:\temp\folder_class_set.log"

################################################################
#                 F U N C T I O N S
################################################################

Function WriteLog ($text, $uname) {
    if ($uname) {
        $xname = $uname + " | "
    } else {
        $xname = "######## | "
    }
    ((Get-Date -Format "dd.MM.yyyy hh:mm:ss").ToString() + " | " + $xname + $text) | Out-File -FilePath $log_file -Append
}

Function DoSubFolder ($folder, $path) {
    $folderView = new-object Microsoft.Exchange.WebServices.Data.FolderView(2147483647)
    $subfolders = $folder.FindFolders($folderView)
    if (!$path) { $path = $folder.DisplayName }
    foreach ($subfolder in $subfolders) {
        if ($subfolder.SearchParameters -ne $null) {
            # If it's a search folder, just skip it
            continue
        }
        $xpath = $path + "\" + $subfolder.DisplayName
        if (!($subfolder.folderClass)) {
            WriteLog ($xpath + " has no FolderClass set! Setting FolderClass to IPF.Note") $name
            ($name + " --> " + $xpath) | Out-File -FilePath $res_file -Append
            $subfolder.FolderClass = "IPF.Note"
            $subfolder.Update()
        } else {
            WriteLog ($xpath + " has FolderClass property of " + $subfolder.folderClass) $name
        }
        DoSubfolder $subfolder $xpath
    }
}

################################################################
#                         S T A R T
################################################################

Add-Type -Path $ex_ews_path
$ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013
$Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)

if (!(Get-Command "Get-ExchangeServer" -EA SilentlyContinue)) {
    WriteLog "Connecting to Exchange using $ex_ps_url"
    $ex_rem_session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri $ex_ps_url -Authentication Kerberos -EA SilentlyContinue -WA SilentlyContinue
    Import-PSSession $ex_rem_session -DisableNameChecking -EA SilentlyContinue -WA SilentlyContinue > $null
} else {
    WriteLog "Exchange environment already established"
}

$mbxs = @(Get-Mailbox -ResultSize Unlimited)
$nmbx = $mbxs.Count
WriteLog ($nmbx.ToString() + " mailboxes to process...")
$i = 0
foreach ($mbx in $mbxs) {
    $mail = $mbx.PrimarySMTPAddress
    $name = $mbx.SamAccountName
    $i++
    WriteLog "Processing mailbox $i of $nmbx ..." $name
    Write-Host ([math]::Round((($i / $nmbx) * 100),2)).ToString() " % $name"
    if ($mail) {
        WriteLog "Primary email address is: $mail" $name
        $Service.AutodiscoverUrl($mail,{$true})
        WriteLog ("Autodiscover URL: " + $Service.Url.OriginalString) $name
        $Service.ImpersonatedUserId = new-object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $mail)
        $fldInbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)
        WriteLog ("Default Inbox folder: " + $fldInbox.DisplayName + " contains " + $fldInbox.TotalCount + " item(s)") $name
        DoSubFolder $fldInbox
    } else {
        WriteLog "Primary email address NOT SPECIFIED! Skipping mailbox..." $name
    }
}

if ($ex_rem_session) {
    Remove-PSSession $ex_rem_session -EA SilentlyContinue -WA SilentlyContinue
}

Damit dieser Code ausgeführt werden kann, benötigt das ausführende User-Account mindestens das Recht, alle Postfächer aufzulisten und deren SAMAccountName und PrimarySMTPAddress zu lesen (also: Mitgliedschaft in irgendeiner Exchange-Administratorrolle) sowie die ApplicationImpersonation-Berechtigung. Da alle Postfächer zu bearbeiten waren, war das Zuweisen dieser Berechtigung denkbar einfach:

New-ManagementRoleAssignment –Name "adminuser_may_impersonate" –Role ApplicationImpersonation –User: "DOMAIN\adminuser"

Noch ein wenig Hinergrund: Die Unterordner im Quellsystem wurden mit einem Skript angelegt, welches EWS Managed API 2.0 und ebenfalls ApplicationImpersonation nutzt. Dabei wurde denkbar einfach vorgegangen:

$oFolder = new-object Microsoft.Exchange.WebServices.Data.Folder($exchService)
$oFolder.DisplayName = "Mailarchiv"
$oFolder.Save([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::inbox)

Die FolderClass-Eigenschaft wurde nicht explizit gesetzt. Offensichtlich erstellt dabei EWS Managed API auch keine Property PR_CONTAINER_CLASS. DELL Migrator hingegen erstellt diese Property offenbar standardmäßig, bestückt sie jedoch strikt mit dem Wert, den sie im Quellsystem hatte. In diesem Fall also: mit keinem.

Happy troubleshooting!

Tags » «

+