Wer Wildcard Zertifikate in Exchange 2013 oder 2016 einsetzen will, muss ein paar Dinge beachten. Microsoft macht es mal wieder etwas komplizierter als nötig.
Für die Aufgabenplanung einen Task mit Hilfe von Powershell erstellen:
$objTime=[datetime]::ParseExact("09.12.2020 03:00:00", "dd.MM.yyyy HH:mm:ss", $null)
$CMD = New-ScheduledTaskAction -Execute """\\Pfad mit Leerzeichen\script.bat"""
$Time = New-ScheduledTaskTrigger -Once -At $objTime
$User = "domain\_svc_taskrunner"
$Password = ""
Register-ScheduledTask -TaskName "Script Install" -Trigger $Time -User $User -Password $password -Action $CMD -AsJob -Force -RunLevel Highest
Eine Remotesitzung über Enter-PSSession muss hergestellt sein. Siehe hier.
$url = "https://installer.bea-brak.de/cs/installation/1/beAClientSecurity-Installation.zip"
$outputPath = "C:\Temp\"
$outputFile = "beAClientSecurity-Installation.zip"
if (-Not(Test-Path "c:\temp")) {
New-Item -Path "c:\" -Name "Temp" -ItemType "directory"
}
Invoke-WebRequest -Uri $url -OutFile (Join-Path -Path $outputPath -ChildPath $outputFile)
Expand-Archive (Join-Path -Path $outputPath -ChildPath $outputFile) $outputPath
$app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -like "bea*" }
$app | foreach-object {$_.uninstall()}
TASKKILL /F /IM beAClientSecurity.exe /T
Remove-Item "C:\Program Files (x86)\BRAK" -Recurse -Force
$runcmd = Join-Path -Path $outputPath -ChildPath "beAClientSecurity-Installation.exe"
Start-Process $runcmd -ArgumentList "-q" -wait
In Teil 1 ging es um das Herstellen der Voraussetzungen für das Remotemanagement. In diesem Teil greifen wir nun auf den Client zu und führen administrative Aufgaben aus.
Einmal findet der Zugriff über den Hostnamen statt und einmal über die IP, falls der Hostname nicht mit der momentanen IP im DNS eingetragen ist.
Zugriff über den Hostnamen
Dies ist die normale Art der Remoteadmnistration. Das funktioniert direkt, sofern dem Client über DNS eine IP zugeordnet ist und der Client den DNS Server erreichen kann, um Kerberos Tickets abzuholen. Wenn diese Voraussetzungen nicht erfüllt sind geht es als Fallback über die IP, siehe weiter unten.
Als Administrator auf Server anmelden, Powershell starten.
Enter-PSSession clientname
Die Shell wechselt bei Erfolg auf den Client und der Prompt wechselt zu soetwas wie
[clientname:] PS C:\users\adminman\Documents>
Zugriff über die IP
Der Zugriff per IP geschieht in mehreren Schritten
- TrustedHosts auf Serverseite definieren
- Credentials abfragen
- Session starten
Zuerst als Administrator auf Server anmelden, Powershell als Administrator starten.
1. TrustedHosts auf Serverseite definieren
cd wsman:\localhost\Client Set-Item TrustedHosts -Value * -Force dir
Ein Sternchen bedeutet alle Hosts sind vertrauenswürdig. Die Einstellung ist Beispielhaft und sollte in einem Produktivsystem angepasst werden. Z.B. immer auf die Client-IP, den man erreichen will. Oder auf die Client DHCP Range.
Man löscht die TrustedHosts mit
Set-Item TrustedHosts -Value "" -Force
2. Credentials abfragen
Beim Zugriff über eine IP muss eine explizite Authentifizierung erfolgen.
$cred = Get-Credential
3. Session starten
Enter-PSSession <ip> -credential $cred -Authentication Negotiate
Fertig
Die Eingabe von Kommandos kann nun erfolgen. Sie werden im Kontext des Benutzers ausgeführt, der den Befehl Enter-PSSession ausgeführt hat. Ein sogenannter second Hop ist nicht möglich. Man kann von dem Client aus keine anderen Computer oder Netzlaufwerke über die Windows-Authentifizierung erreichen. HTTP und HTTPS geht aber zum Beispiel.
In diesem Zusammenhang ein paar Scriptebeispiele, die in einer solchen Sitzung Sinn machen:
Remotesupportsoftware bietet nicht immer die Möglichkeit administrative Aufgaben durchzuführen. Man Programme also nur im Kontext des aktuell angemeldeten Benutzers ausführen. Bei Teamviewer, Teams und diversen anderen Programmen, bei denen man den Desktop freigeben kann, ist dies der Fall.
Eine Ausnahme sei erwähnt: der Fastviewer Client kann das. Soll keine Werbung sein, ist nur der einzige Client den ich kenne, der das kann.
Hier eine Anleitung zur vorbereitung der Client für PowerShell Remotemanagement über VPN. Das ist eine besondere Form, da man Remotesitzungen in Powershell normalerweise über den Hostnamen durchführt. Dazu muss der Client mit der IP im DNS registriert sein. Das ist bei VPN-Verbindungen oft nicht der Fall. Deshalb zeige ich in diesem Artikel auch wie man über eine IP per Powershell auf einen Client zugreifen kann und welche Voraussetzungen dafür erfüllt sein müssen.
Zuerst werden die nötigen Gruppenrichtlinien ausgerollt:
- Firewallausnahmen auf dem Client setzen
- WinRM Dienst starten
- TrustedHost Liste für die Clients definieren (für Zugriff über IP nötig)
- Ausführungsrichtlinie bestimmen
Diese Gruppenrichtlinien sind alle für die Clients bestimmt. Also in der entsprechenden OU ausrollen.
1. Firewallausnahmen auf dem Client setzen
Diese Art der Firewallkonfiguration funktioniert erst ab Windows 10. Davor musste man die Ports über die folgende GPO konfigurieren Richtlinien->Administrative Vorlagen->Netzwerk-Netzwerkverbindungen->Windows Defender Firewall->{Profil}->Eingehende Portausnahme festlegen.
Hier ein Beispiel für WSMan:

Also los gehts!
Richtlinien->Windows-Einstellungen->Sicherheitseinstellungen->Windows-Firewall mit erweiterter Sicherheit->Eingehende Regeln
Rechte Maustaste->Neue Regel
Vordefinierte Regel->Windows-Remoteverwaltung->HTTP eingehend

Dann mit der rechten Maustaste auf die eben erstellte Regel klicken, den Bereich einschränken und bei Bedarf auch für das öffentliche Netzwerk freigeben. Man sollte auf jeden Fall den Bereich der Server einschränken, über die man die Remoteverwaltung durchführen kann:


Ping aktiviert man mal besser gleich mit:
Rechte Maustaste->Neue Regel
Vordefinierte Regel->Datei- und Druckerfreigabe->ICMPv4 eingehend

WinRM Dienst starten
Einstellungen->Systemsteuerungseinstellungen->Dienste
Rechts Mausteste->Neu->Dienst


3. TrustedHost Liste für die Clients definieren (für Zugriff über IP nötig)
Richtlinien->Administrative Vorlagen->Windows-Komponenten->Windows-Remoteverwaltung (Windows Remote Management, WinRM)->WinRM-Client->Vertrauenswürdige Hosts
Die Eingabemöglichkeiten sind gut erklärt. Man kann mit Domänennamen arbeiten oder mit IP-Adressen. In meinem Fall habe ich beides gemacht und eingetragen: *.domain.com, 10.0.1.*
Die Domaine und die Ip’s sind natürlich individuell anzupassen. In meinem Fall ist die IP Range 10.0.1.* Mein Serverbereich. Man kann also nur von den Servern aus auf die Clients per PowerShell unter Angabe der IP zugreifen.
4. Ausführungsrichtlinie bestimmen
Bei PowerShell gibt es sogenannte Ausführungsrichtlinien. Diese bestimmen ob ein Script unsigniert ausgeführt werden darf. Mehr dazu hier:
Wir definieren, dass alle Scripte, die nicht lokal ausgeführt werden, signiert seien müssen. Die nächsthöhere Sicherheitsstufe ist, dass alle Script signiert seien müssen. Stichwort Set-AuthenticodeSignature.
Richtlinien->Administrative Vorlagen->Windows-Komponenten->Windows PowerShell->Scriptausführung aktivieren

Weiter geht es mit Teil 2:
Bei der Übertragung von Faxen über Voice over IP gibt es immer wieder Probleme. Da das Thema immer mal wieder aufkommt hier eine Dokumentation der wichtigsten Erkenntnisse.
WinRM wird von Powershell genutzt, um Remoteverbindungen herzustellen. Das läuft normalerweise über HTTP und Port 5985. Die Daten werden laut Dokumentation trotzdem verschlüsselt übertragen. Trotzdem birg dieses Verfahren Sicherheitsrisiken, die man durch Verwendung von SSL ausschließen kann. Daher wird die Nutzung von SSL für manche Verfahren vorausgesetzt. WinRM über SSL läuft über den Port 5986. Hier eine kurze Anleitung, wie man das über die eigene AD Zertifizierungsstelle realisiert.
Auf einer neue Partition die Ordnerstruktur erstellen. Z.B.:
D:\Microsoft Exchange\Queue
Exchange Management Shell öffnen und folgendes Kommando eingeben:
Move-TransportDatabase.ps1 -QueueDatabasePath: "D:\Microsoft Exchange\Queue" -QueueDatabaseLoggingPath: "D:\Microsoft Exchange\Queue"
Dieses Script führt die Verschiebeanforderung für die Datenbank und die Logs durch. Die Datenbank und die Logs erscheinen nach der Verschiebung am neuen Speicherort.
Regel #1: nutze nicht win32_product!!!
So gut wie alle Beispiele im Netz beziehen sich auf die WMI Provider win32_product. Abrufbar durch:
Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -like "*Software*" }
oder
Get-CimInstance win32_product | Where-Object { $_.Name -like "*Software*" }
Es gibt gute Gründe, die dagegen Sprechen:
- Durch den Aufruf wird für jedes einzelne Produkt eine Konsitenzprüfung durchgeführt. Und wenn die fehlschlägt wird sogar noch eine Reparaturinstallation im Hintergrund versucht!!
- Deswegen ist der Aufruf auch ultralangsam
- Es wird nur Software aufgeführt, die durh einen Windows installer installiert wurde. Und das nutzt längst nicht jede Software
Zu Punkt 1 siehe:
Damit ist win32_product für alle Zeiten disqualifiziert!
Ja aber wie machen wir es denn jetzt?
Über die Registry!
Am einfachsten und am schnellsten geht es über die Registry und das ist auch der Weg, wie die Liste unter Programme hinzufügen/ändern erstellt wird. Es gibt Pfade für 32 Bit Apps und für 64 Bit Apps.
$Apps = @()$Apps += Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"$Apps += Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"$Apps | Out-Gridview
Den Tip mit der Registry habe ich von folgendem genialen Blog:
https://xkln.net/blog/please-stop-using-win32product-to-find-installed-software-alternatives-inside/
Dort wird auch beschriben, wie man Software ermittelt, die im Benutzerprofil unter AppData installiert ist. Sicherheitshalber hier per Copy&Paste, falls das Blog mal offline geht. Die Info wird für jeden Admin irgendwann Gold wert sein:
Finding apps that install into AppData
There has been a growing trend of application vendors making installers that deploy to a user’s profile (%userprofile%\AppData). This is commonly done (much to the dismay of the IT departments) to allow users to install programs without needing administrative privileges.
These applications will also have their installation documented in the registry, but under HKEY_CURRENT_USER instead of HKEY_LOCAL_MACHINE.
This poses a few challenges. Each user’s registry hive is located in their profile as %userprofile%\NTUSER.DAT.
- If a user is logged in, this can be accessed by other (administrative) users on the system via the
HKEY_USERS\$ACCOUNT_SIDkey - If a user is not logged in, the hive can be manually mounted using
REG LOAD.
One catch is that if a user’s registry hive is already loaded (i.e., they are logged in) it cannot be loaded again as we will get a The process cannot access the file because it is being used by another process. error.
So we’ll need to enumerate a list of profiles in the system, determine whether we need to load their registry hive, mount it if we need to, pull the application install data, and finally unload the hive. The last part is important, failing to do so will leave the user unable to log in due to the same error we encountered above.
The first part of finding a list of profiles and determining whether they’re currently loaded is made easy by quering Win32_UserProfile
localpath sid loaded special
--------- --- ------ -------
C:\Users\.NET v4.5 Classic S-1-5-82-3876422241-1344743610-1729199087-774402673-2621913236 False False
C:\Users\.NET v4.5 S-1-5-82-271721585-897601226-2024613209-625570482-296978595 False False
C:\Users\DefaultAppPool S-1-5-82-3006700770-424185619-1745488364-794895919-4004696415 False False
C:\Users\MSSQL$MICROSOFT##WID S-1-5-80-1184457765-4068085190-3456807688-2200952327-3769537534 True False
C:\Users\test2 S-1-5-21-1543284909-1794992621-2893585182-1019 False False
C:\Users\test1 S-1-5-21-1543284909-1794992621-2893585182-1017 False False
C:\Users\md S-1-5-21-1543284909-1794992621-2893585182-1000 True False
C:\WINDOWS\ServiceProfiles\NetworkService S-1-5-20 True True
C:\WINDOWS\ServiceProfiles\LocalService S-1-5-19 True True
C:\WINDOWS\system32\config\systemprofile S-1-5-18 True True
There are some key pieces of information we need to extract from this output
- The
loadedparameter tells us if the profile (and therefore hive) is loaded - The
specialparameter tells us whether this is a system account – we can ignore those - The
sidparameter gives us some clues on what type of account we’re dealing with.
Normal user accounts are prefixed with S-1-5-21, which matches the Microsoft documentation on Well known security identifiers.
Using this info we can put together a smarter function that pulls system wide installed applications, as well as those deployed across all user profiles. Using parameter sets we can allow the user to pull various combinations of data, though some will require administrative privileges:
| Parameter | Requires Admin | Data Returned |
|---|---|---|
| Global | No | Globally installed applications |
| GlobalAndAllUsers | Yes | Globally installed applications and all user installed applications. Default. |
| GlobalAndCurrentUser | No | Globally installed applications and applications installed under the profile of the user executing the function |
| CurrentUser | No | Applications installed under the profile of the user executing the function |
| AllUsers | Yes | All user installed applications |
And here is the function:
function Get-InstalledApplications() {
[cmdletbinding(DefaultParameterSetName = 'GlobalAndAllUsers')]
Param (
[Parameter(ParameterSetName="Global")]
[switch]$Global,
[Parameter(ParameterSetName="GlobalAndCurrentUser")]
[switch]$GlobalAndCurrentUser,
[Parameter(ParameterSetName="GlobalAndAllUsers")]
[switch]$GlobalAndAllUsers,
[Parameter(ParameterSetName="CurrentUser")]
[switch]$CurrentUser,
[Parameter(ParameterSetName="AllUsers")]
[switch]$AllUsers
)
# Excplicitly set default param to True if used to allow conditionals to work
if ($PSCmdlet.ParameterSetName -eq "GlobalAndAllUsers") {
$GlobalAndAllUsers = $true
}
# Check if running with Administrative privileges if required
if ($GlobalAndAllUsers -or $AllUsers) {
$RunningAsAdmin = (New-Object Security.Principal.WindowsPrincipal([Security.Principal.WindowsIdentity]::GetCurrent())).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if ($RunningAsAdmin -eq $false) {
Write-Error "Finding all user applications requires administrative privileges"
break
}
}
# Empty array to store applications
$Apps = @()
$32BitPath = "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*"
$64BitPath = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"
# Retreive globally insatlled applications
if ($Global -or $GlobalAndAllUsers -or $GlobalAndCurrentUser) {
Write-Host "Processing global hive"
$Apps += Get-ItemProperty "HKLM:\$32BitPath"
$Apps += Get-ItemProperty "HKLM:\$64BitPath"
}
if ($CurrentUser -or $GlobalAndCurrentUser) {
Write-Host "Processing current user hive"
$Apps += Get-ItemProperty "Registry::\HKEY_CURRENT_USER\$32BitPath"
$Apps += Get-ItemProperty "Registry::\HKEY_CURRENT_USER\$64BitPath"
}
if ($AllUsers -or $GlobalAndAllUsers) {
Write-Host "Collecting hive data for all users"
$AllProfiles = Get-CimInstance Win32_UserProfile | Select LocalPath, SID, Loaded, Special | Where {$_.SID -like "S-1-5-21-*"}
$MountedProfiles = $AllProfiles | Where {$_.Loaded -eq $true}
$UnmountedProfiles = $AllProfiles | Where {$_.Loaded -eq $false}
Write-Host "Processing mounted hives"
$MountedProfiles | % {
$Apps += Get-ItemProperty -Path "Registry::\HKEY_USERS\$($_.SID)\$32BitPath"
$Apps += Get-ItemProperty -Path "Registry::\HKEY_USERS\$($_.SID)\$64BitPath"
}
Write-Host "Processing unmounted hives"
$UnmountedProfiles | % {
$Hive = "$($_.LocalPath)\NTUSER.DAT"
Write-Host " -> Mounting hive at $Hive"
if (Test-Path $Hive) {
REG LOAD HKU\temp $Hive
$Apps += Get-ItemProperty -Path "Registry::\HKEY_USERS\temp\$32BitPath"
$Apps += Get-ItemProperty -Path "Registry::\HKEY_USERS\temp\$64BitPath"
# Run manual GC to allow hive to be unmounted
[GC]::Collect()
[GC]::WaitForPendingFinalizers()
REG UNLOAD HKU\temp
} else {
Write-Warning "Unable to access registry hive at $Hive"
}
}
}
Write-Output $Apps
}
Example usage
# Find installed applications installed globally and inside all user profiles (default behavior) and export to a CSV
Get-InstalledApplications | Select DisplayName, InstallLocation | Export-Csv AllApps.csv -NoTypeInformation
# Find installed applications within user profiles
Get-InstalledApplications -AllUsers | Select DisplayName, InstallLocation
# Find installed applications within the current user profile
Get-InstalledApplications -CurrentUser | Select DisplayName, InstallLocation
Get-Package
The last and probably most convenient option is Get-Package, but as is the way, there are a few caveats.
- You must be running PowerShell 5.1 or newer
- It won’t pull applications installed into user profiles that are not the user running the command
That is to say, Get-Package will detect:
- Globally installed applications
- Applications installed into the user profile of the user running the command
Get-Package returned 1345 items, but the vast majority of the extra rows were various updates (Windows Defender Security and Intelligence updates, Windows Malicious Software Removal Tool updates, monthly Cumulative Updates – you get the picture ).
Interestingly, there were a few NVIDIA applications under the HKLM path that my function above pulled but were not present in the Get-Package output (NVIDIA Display Session Container, NVIDIA Display Session Container, NVIDIA Control Panel, and a bunch more).
Get-Package also returned PowerShell modules installed via the PowerShell Gallery.

Lastly, it also returned a few applications that had previously been uninstalled, though evidently they still left some traces behind.
Hopefully this provides everyone with faster and safer ways to query for installed applications. Remember, friends don’t let friends query Win32_Product.
https://www.youtube.com/watch?v=4V0NNZzNrx4