Wenn auf einen RPD-Zugang, aus dem Internet ohne ihn durch eine VPN-Tunnel zu schleusen, nicht verzichtet werden kann/will gibt es kein Möglichkeit den Zugang zu Filtern. Der Router leitet alle RDP-Anfragen an den Server weiter wenn dies so eingerichtet ist.
Mit ein paar keine Skripten kann aber dennoch überprüft werden ob zumindest die IP von welcher die Anmeldung erfolgt erlaubt ist und / oder ob der PC-Name korrekt ist. Wenn keine feste IP des RDP-Users bekannt ist gibt es noch die Möglichkeit via Dyn(namisches) DNS die IP zu Verifizieren. Aber dabei muss auf Userseite der DynDNS-Eintrag vor dem Verbindung erfolgen z.B. via Router ;-)
Des weiteren muss in den Überwachungs-Richtlinien das Anmelden eingeschaltet werden. Das geht über die lokalen Gruppenrichtlinien (gpedit.msc).
Das Überprüfen setzt allerdings voraus dass der User Administrator-Rechte hat. Doch gerade bei Server-System ist zwingend davon abzuraten einem User Admin-Rechte zu geben.
Es bleibt dann nur die Möglichkeit über ein Hilfsprogramm zeitweise Admin-Rechte zu bekommen.
Es werden 2 Beispiele mit RunAsTool und RunAsRob aufgezeigt. - RunAsRob funktioniert in einer produktiven Umgebung nur mit einer gekauften Version! Sonst verlangt auf RusAsRob jedes mal eine Bestätigung zur Ausführung - RunAsTool funtioniert nur wenn die Benutzerkontensteuerung UserAccountControlSettings.exe auf "Nie benachrichtigen" eingestellt ist sonst muss immer der Bestätigung-Dialog beantwortet werden. |
Der Vorgang besteht aus mehreren Teilen:
A) Eine Aufgabenplanung die bei der Anmeldung getriggert wird. Login und Relogin löses ein Startskript aus das nur relativ kurz sichtbar ist (Geht leider nicht ohne sichtbares CMD bzw. Powershell Fenster)
B) Ein Startskript welches durch die Aufgabenplanung gestartet wird.
C) Ein Powershell-Skript welches das Ereignis-Log auswertet (Hier werden die Admin-Rechte benötigt). Realisiert wird das via RunAsTool bzw. RunAsRob.
D) Ein zweites Powershell-Script liest die Info's aus dem ersten Skript ein und wertet die Einträge für den angemeldeten User aus. Danach wird in eine zweite Logdatei die Auswertung geschrieben und je nach Einstellung ein Email versendet und der User abgemeldet.
E) Ein weiteres Powershell-Skript kann dann ggf. das Benutzerkonto sperren und ein relogin verhindern.
Hier nun die einzelnen Punkte erklärt
A) Aufgabenplanung erstellen => Aufgabe speichern |
|
B) Dieses Skript (run_initial.vbs bzw. (run_initial.ps1) hat nur die Aufgabe das folgende Skript (run_hidden.ps1) versteckt zu starten und enthält nur einen Befehl. Somit ist die Ausführungszeit recht kurz und das Fenster ebenfalls nur kurz zu sehen.
Das 2. Script (run_hidden.ps1) ruft wiederum das Skript um das Ereignis Protokollauszulesen (loginevents.ps1) auf und wartet bis es abgeschlossen ist. Danach das Script (checkuser.ps1) um die Zugangsparameter zu überprüfen.
C) Das Login-Evens-Skript durchläuft eine vorgegebene und gefilterte Anzahl von Ereignissen (ID 4624,4778) und schreibt das Ergebnis in eine Text-Datei (loginevents.txt)
- Username
- PC-Name RDP-Client (Smartphone- und iPad-Namen werden nicht in das Event-log eingetragen!).
- IP-Adresse
- Ameldezeitpunkt
- Art des Logins
D) Das Überprüfe-Benutzer-Skript (checkuser.ps1) holt die Infos des eigenen Benutzers aus der (loginevents.txt) und vergleicht diese mit den Vorgaben.
- IP-Adresse (einzelne oder P-Bereich
- DynDNS-Eintrag
- Clinet-Name
Nach der Überprüfung kann bei Unstimmigkeiten folgende Aktionen ausgeführt werden:
- nicht machen
- Info-Email versenden (1 oder mehrere Empfänger)
- Benutzer abmelden
- Benutzerkonto sperren (zusätzlich zum abmelden)
Zusaätzlich wird ein Ereignis-Log erstellt werden:
- Alle Logins protokollieren
- Nur Ereignisse Protokollieren welche Ungereimtheiten hatten
Zusatz-Funktion
- Für alle die den Kriterien Entsprochen haben kann eine Pop-Up-Box angezeigt werden. Einfach eine Datei mit Inhalt erzeugen (clientinfo.txt)
E) Sollte die Überprüfung ergeben dass der Login von einen falschen PC oder IP-Adresse erfolgt ist kann das Konto deaktiviert werden (deactivateuser.ps1).
Hier die Scripte
run_initial.ps1
# Startscript (Kann nicht versteckt ausgeführt werden) if ([string]::IsNullOrEmpty($PSScriptRoot)) {$PSScriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition} # Powershell 2.0 $agl = $PSScriptRoot + '\run_hidden.ps1' Start-Process -FilePath 'powershell.exe' -ArgumentList $agl -WindowStyle Hidden
run_initial.vbs
' CSCRIPT 'CreateObject("Shell.Application").ShellExecute "cscript.exe", " /NoLogo " & Replace(WScript.ScriptFullName, WScript.ScriptName, vbNullString) & "run_hidden.vbs", vbNullString, vbNullString, 0 ' Powershell CreateObject("Shell.Application").ShellExecute "powershell.exe", " -NoLogo " & Replace(WScript.ScriptFullName, WScript.ScriptName, vbNullString) & "run_hidden.ps1", vbNullString, vbNullString, 0
run_hidden.ps1
<#----------------------------------------------------------------------------------------- Dieses Script wird vom 1. Startscript run_initial.vbs (.ps1) aufgerufen. Ab jetzt wird alles unsichtbar ausgeführt. Es wird ein Hilfsprogramm benötigt um das Powershell-Script 'loginevents.ps1' mit Administrator-Rechten zu starten Es sind 3 Tools als bsp. aufgeführt - RunAsRob Funktioniert nur mit einer lizenzierten Version! Alle Parameter müssen in RunAsRob eingetragen und dort die .xus erstellt werden. Passwort wird nicht entschlüsselt - RunAsTool Einstellung zur Benuterkontensteuerung muss auf 'nie Benachrichtigen' gesetzt werden. Im Suchenfeld UAC eingeben Freeware > https://www.sordum.org/8727/runastool Alle Parameter müssen im RunAsTool eingetragen und ein Link erzeugt werden. Der Programmpfad und Start-Parameter stehen in diesem Link - PsExec PsTools > https://docs.microsoft.com/en-us/sysinternals/downloads/pstools Das Programm ist eigentlich dafür gedacht auf einem entfernten PC/Server als ein bestimmter Benutzer etwas auszuführen. Hier wird der eingene PC/Server verwendet Zu Beachten: Das Passwort wird während des Scripts dekodiert und kann ausgelesen werden! Version 1.8 -----------------------------------------------------------------------------------------#> $agpS = 'powershell.exe' if ([string]::IsNullOrEmpty($PSScriptRoot)) {$PSScriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition} # Powershell 2.0 #----------------------------------------------------------------------------------------------------------------------- # RunAsRob $appRunAsAdmin = $PSScriptRoot + '\RunasRob.exe' #Pfad zum Programm RunAsRob $argRunAsAdmin = $PSScriptRoot + '\loginevents.xus' #----------------------------------------------------------------------------------------------------------------------- # RunAsTool #$appRunAsAdmin = $PSScriptRoot + '\RunAsTool_x64.exe' #"C:\Program Files\RunAsTool\RunAsTool.exe" #$argRunAsAdmin = 'ehq' # Steht in der Verknüpfung (durch RunAsTool erstellt) #----------------------------------------------------------------------------------------------------------------------- # PsExec #$appRunAsAdmin = $PSScriptRoot + '\PsExec64.exe' #'c:\checkrdp\PsExec64.exe' #Pfad zum Programm PSExec #$admin = 'administrator' # Benutzer #$pwd = $PSScriptRoot + '\admin.cred' # Passwort oder Pfad zur Datei welche mit 'create_credentials.ps1' erzeugt wurde #$pwd = "nicht so geheimes PW ;-)" #if (Test-Path $pwd) { # In $pwd steht der Pfad mit den Passwort-Credentials # $argRunAsAdmin = '\\' + $Env:COMPUTERNAME, '-u ' + $admin, ('-p ' + [System.Runtime.InteropServices.Marshal]::PtrToStringUni([System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode((GET-Content $pwd | ConvertTo-SecureString)))), $agpS, ($PSScriptRoot + '\loginevents.ps1') #} else { # $argRunAsAdmin = '\\' + $Env:COMPUTERNAME, '-u ' + $admin, "-p $pwd", $agpS, ($PSScriptRoot + '\loginevents.ps1') #} #----------------------------------------------------------------------------------------------------------------------- # Auslesen des Event.logs mit Administrator-Rechten Start-Process -FilePath $appRunAsAdmin -ArgumentList $argRunAsAdmin -Wait -WindowStyle Hidden # Benutzer überprüfen $arg = $PSScriptRoot + '\checkuser.ps1' Start-Process -FilePath $agpS -ArgumentList $arg -WindowStyle Hidden
loginevents.ps1
<#------------------------------------------------------------------------------------------------------------------------------------- Das Script liest die Event Einträge des Sicherheits-Logs aus. Es muss mit Administrator-Rechten ausgeführt werden! Info: Ereignisanzeige starten -> %windir%\system32\eventvwr.msc /s (Filter auf die EventID's 4624,4778 setzen) Folgene Info's werden ausgelesen - Benutzer-Name - Client-Name (nicht immer vorhanden. z.B Login mit Android, MacOS) - IP-Adresse des Clients - Datum & Uhrzeit - Art des Logins - Logische Info-Eintag über den User bis dieser vollständig eingelesen wurde. Das Log wird chronologisch rückwärts durchlaufen. Der letzte Eintrag ist Ereignis 10 und der erste Ereignis 3 die ausgewertet werden. Es kommt vor dass ein Ereigis 3 nicht erzeugt wird Ein Relogin erzeugt nur ein Eintrag. Vier Typen können ausgelesen werden. Login, Relogin, fehlgeschlagene Logins und Logout. Im Standard-Betrieb werden aber nur die erfolgreichen Login und Relogins benötigt. Script-Ausführungszeit Je nach Anzahl der geichzeitigen Logins muss die $newest Variable in der Funktion GET-LoginInfoOfUsers angepasst werden. Je größer desto länger dauert das Auslesen der Ereignisse. 50-100 sollten ausreichend sein. Version 2.0 --------------------------------------------------------------------------------------------------------------------------------------#> if ([string]::IsNullOrEmpty($PSCommandPath)) { # Powershell 2.0 $PSCommandPath = $MyInvocation.MyCommand.Definition $PSScriptRoot = Split-Path -Parent -Path $PSCommandPath } # Dateipfad der Auswertung (Namensgleich dem Script). $loginevents = $PSCommandPath.Replace('.ps1', '') + '.txt' $deactivateuserPath = (Split-Path -Parent -Path $PSScriptRoot) + '\deactivateuser.txt' # Müssen identisch mit chechuser.ps1 sein! $sep = '|' # Feld-Trennzeichen "`t" (Tabulator) $missing = '-?-' # Eintrag wenn ein Wert nicht ermittelt werden kann Function GET-LoginInfoOfUsers { [CmdletBinding()] Param ( [Parameter(Mandatory=$false,Position=0)] [String]$hostname=$Env:COMPUTERNAME, [Parameter(Mandatory=$false,Position=1)] [Int32]$newest=20, [Parameter(Mandatory=$false,Position=2)] [Int64[]]$eventtyp=(4624,4778), #4625 #4634 [Parameter(Mandatory=$false,Position=3)] [String]$username ) #In dieses Array werden die Login-Infos eingetragen # 0 -> Username # 1 -> PC-Name (Client) # 2 -> IP-Adresse # 3 -> Ameldezeitpunkt # 4 -> Art des Logins # 5 -> Hilfsfeld für die Loginerfassung ob die Informationen schon komplett sind $arrUser = @() $events = get-eventlog security -InstanceID $eventtyp -ComputerName $hostname -newest $newest # Sollte testweise ein bestimmter Zeitabschnitt geprüft werden. Benötigt deulich länger um ein Ergebnis zu liefern #$Begin = Get-Date '13/9/2019 16:15:00' #$End = Get-Date '13/9/2019 16:16:00' #$events = get-eventlog security -InstanceID $eventtyp -ComputerName $hostname -After $Begin -Before $End # Die einizelnen Ereignisse durchlaufen foreach($event in $events) { # In Message steht alles $Message = $event.Message $arrMessage = ($Message -split "`n").Trim() $kontoname = ($arrMessage -match 'Kontoname:').replace('Kontoname:', '').Trim() # English 'Account Name:' oder? 'TargetUserName:' #$s =($kontoname -join ' | ') + ' | ' + $event.InstanceId + ' | ' + (Get-Date -format 'hh.mm.ss') $locked = $false $ip = '' switch ($event.InstanceId) { 4624 { # Bei einem Login werden mehrere Ereignisse protokolliert $user = $kontoname[1] $atyp = ($arrMessage -match 'Anmeldetyp:').replace('Anmeldetyp:', '').Trim() # English 'LogonType:' if ($atyp -eq 3) {$locked = $true} $pc = ($arrMessage -match 'Arbeitsstationsname:').replace('Arbeitsstationsname:', '').Trim() # English 'WorkstationName:' $ip = ($arrMessage -match 'Quellnetzwerkadresse:').replace('Quellnetzwerkadresse:', '').Trim() # English 'IpAddress:' $login = 'login' break } 4778 { # Bei einem Relogin gibt es nur einen Eintrag im Ereignisprotokoll. Wird nach dem Login-Ereignis erzeugt $atyp = 10 # Damit ein neuer Eintrag erzeugt wird $user = $kontoname $pc = ($arrMessage -match 'Clientname:').replace('Clientname:', '').Trim() $ip = ($arrMessage -match 'Clientadresse:').replace('Clientadresse:', '').Trim() $locked = $true $login = 'relogin' break } 4634 {# Abmelden $user = $kontoname $atyp = ($arrMessage -match 'Anmeldetyp:').replace('Anmeldetyp:', '').Trim() # English 'LogonType:' $pc = ($arrMessage -match 'Kontodomäne:').replace('Kontodomäne:', '').Trim() # English 'Account Domain:' $login = 'logout' break } 4625 {# Fehlgeschlagene Logins $user = $kontoname[1] $login = 'faillogin' $locked = $true break } } $j = -1 # Vorbelegen falls es noch kein Eintrag gibt $ac = $arrUser.count - 1 for ($i=0; $i -le $ac; $i++) { if ($arrUser[$i][0] -eq $user) { $j = $i break } } if ($pc -As [IPAddress] -AS [Bool]) { # Steht in $pc eine IP-Adresse, generiert durch Smartphones, diese ausstreichen $pc = $missing } elseif ($pc.ToLower() -eq $Env:COMPUTERNAME.ToLower()) { # Steht im $pc der Server-Name diesen ebenfalls ausstreichen $pc = $missing } if($j -eq -1) { if ($atyp -eq 10) {# Das Ereignis 10 kommt immer als letztes bei einer erfolgreichen Anmeldung. Deshalb den DS erzeugen $time = $event.TimeGenerated.ToString("dd.MM.yyyy HH:mm:ss") #.TimeWritten $arrUser+=(,($user, $pc, $ip, $time, $login, $locked, "")) } } elseif ($atyp -eq 3 -and !($arrUser[$j][5])) {# Das Ereignis 3 ist die erste Loginereignis welches ausgewertet wird $arrUser[$j][1] = $pc # Hier steht meist der Remote-PC-Name drin. Nicht von Smartphones $arrUser[$j][5] = $true } } foreach($s in $arrUser) { $array_user += ($s -join $sep) + "`n" } $array_user } function write_content() { try {set-Content -Value $arr_user -Path $loginevents} catch { Start-Sleep -Milliseconds 250 # Eine 1/4 Sek. warten und nochmal versuchen den Inhalt zu speichern set-Content -Value $arr_user -Path $loginevents } } $arr_user = GET-LoginInfoOfUsers write_content #cls #Write-Host $arr_user #Write-Host 'Fertig'
checkuser.ps1
<# Das Script liest die Informationen welches mit dem loginevnets.ps1 Script erstellt wurde ein und Vergleicht diese mit den Vorgaben. Es können folgende Informationen des angemeldeten Benuzters verglichen werden. - IP des Clients * Interne(r) IP-Bereich(e). Wenn z.B. via VPN auf den Server zugegriffen wird * Externe IP, Fest oder via Dyn-DNS - Computer-Name Sollte die Überprüfung nicht mit den Kriterien übereinstimmen kann ein Email an den User/ Admin und/oder andere Benuzter versendet werden #> if ([string]::IsNullOrEmpty($PSCommandPath)) { # Powershell 2.0 $PSCommandPath = $MyInvocation.MyCommand.Definition $PSScriptRoot = Split-Path -Parent -Path $PSCommandPath } # Hier wird die Benutzer-Überprüfung gespeichert # Im Pfad des Scripts $LogFile = $PSCommandPath.Replace('.ps1', '.txt') # Eigener Pfad #$LogFile = 'c:\eigenerPfad' + $PSCommandPath.Replace($PSScriptRoot,'').Replace('.ps1', '.txt') # für jeden Benutzer im eingenen Homedrive #$LogFile = $env:HOMEDRIVE + $env:HOMEPATH + $PSCommandPath.Replace($PSScriptRoot,'').Replace('.ps1', '.txt') # Info-Datei wenn der Benutzer deaktiviert werden soll im run_hidden-Script $deactivateuserPath = (Split-Path -Parent -Path $LogFile) + '\deactivateuser.txt' # Soll dem User nach erfoilgreicher Anmeldung ein PopUp angezeigt werden muss in dieser Datei der PopUp-Text stehen $clientinfo = $PSScriptRoot + '\clientinfo.txt' # Sollen alle Login-Ereignisse protokolliert werden $All = $true # Soll eine Email bei Ungereimtheiten versendet werden $sendmail = $false # Was soll getan werden wenn Unstimmigkeiten vorkonmmen # Hier kann die Aktion vorbelegt und in der Funktion CheckUser als Default-Rückgabewert festgelegt werden # -1 = OK (nicht empfohlen) # 0 = Email (Info-Email versenden) # 1 = Logout (Abmelden) # 2 = Konto (RDP-Zugriff deaktivieren) $logoff = 0 # IP-Bereiche (Klasse C) # Nur die ersten 3 Blöcke eintragen $IPsubStringBranch1 = '192.168.0' # Wenn mehr als ein Standort verwendet wird $IPsubStringBranch2 = '192.168.1' $IPsubStringBranch3 = '192.168.2' # IP-Bereiche (Klasse B) # Nur die ersten 3 Blöcke eintragen #$IPsubStringBranch1 = '172.16' # Wenn mehr als ein Standort verwendet wird #$IPsubStringBranch2 = '172.16' #$IPsubStringBranch3 = '172.31' #... $IPsubStringBranches = $IPsubStringBranch1, $IPsubStringBranch2, $IPsubStringBranch3 #, ... # Email Einstellungen $To =Diese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein. '; $Cc = ''; $Bcc = '' $Subject = 'RDP Login-Ereignis' $Body = "Hallo Admin,`nes gab ein erfolgreiches Login. Aber die IP-Adresse bzw. der Client-Name sind nicht hinterlegt.`nBitte umgehend prüfen!`n`nViele Grüße`nLogin-Script`n`n" $BodyIsHTML = $false # Datei-Anhang $AttachmentPath = '' # Mail Server settings $From = $To #Diese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein. ' $SMTPUser = $From #'username' #$SMTPPassword = 'eigenespwvergeben' # Hier das Passwort ODER den Datei-Pfad. Diese Datei wird mit 'createedentials.ps1' erzeugt $SMTPPassword = $PSScriptRoot + '\email.cred' $SMTPServer = 'sslout.df.eu' $SMTPServerPort = 465 #587 25 # In diesem Log wurden die Events gespeichert aus dem loginevensts.ps1 Script (Dateiname). $loginevents = $PSScriptRoot + '\loginevents.txt' $sep = '|' # Feld-Trennzeichen "`t" (Tabulator) $missing = '-?-' # Eintrag wenn ein Wert nicht ermittelt werden kann # --------------------------------------------------------------------- # Funktionen # --------------------------------------------------------------------- function CheckUser { <# .SYNOPSIS Returns the user status. -1 = OK 0 = Email 1 = Logout .DESCRIPTION checks user if loginparameter matches guidelines .EXAMPLE CheckUser -UserName $UserName -ClientPCName $ClientPCName -ClientIP $ClientIP -Return $logoff .OUTPUTS -UserName $UserName -ClientPCName $ClientPCName -ClientIP $ClientIP -Return $logoff #> [CmdletBinding()] Param ( [Parameter(Mandatory=$false,Position=0)] [String]$ClientPCName=$Env:COMPUTERNAME, [Parameter(Mandatory=$false,Position=1)] [String]$UserName=$Env:USERNAME.ToLower(), [Parameter(Mandatory=$false,Position=2)] [String]$ClientIP, [Parameter(Mandatory=$false,Position=3)] [int]$Return=$logoff ) if ($ClientIP -eq $missing) { $IPSubStringClient = $ClientIP } else { $arr = $ClientIP.Split('.') $arr = $arr[0..($arr.Count-2)] $IPSubStringClient = $arr -join '.' #$IPSubStringClient = $ClientIP.Substring(0, $ClientIP.LastIndexOfAny('.')) } switch ($UserName) { # Administrator kann sich von alles IP-Bereichen einloggen 'internal_admin' { if ($IPsubStringBranches -eq $IPSubStringClient) { $Return = -1 } break } # Benutzer kann sich extern von einem bestimmten PC mit Namen z.B. 'externerPCName' und seinem iPhone einwählen 'external_user' { if ('externerPCName', 'users-iPhone-name' -eq $ClientPCName) { $Return = -1 } break } # Benutzer kann sich extern von einer festen IP oder DynDNS-Eintrag einloggen. # DynDNS-Eintrag wird z.B von der Fritzbox des Benutzers aktualisiert. 'external_user2' { if ('93.125.45.12' -eq $ClientIP) { $Return = -1 } elseif ($IP = IPFromDynDns -DynDNS 'mypersonal.dyndns.org') { $Return = -1 } break } # Mehrere Benutzer der Filiale 2 die sich nur aus dem IP-Bereich der Filiale 2 einloggen dürfen # Es wird eine Email versendet wenn die Benutzer sich aus anderen Bereichen eingeloggen ('user1_Branch2', 'user2_Branch2', 'user3_Branch2' -eq $_) { # if ($IPsubStringBranch2 -eq $IPSubStringClient) { $Return = -1 } elseif ($IPsubStringBranch1, $IPsubStringBranch3 -eq $IPSubStringClient) { $Return = 0 } break } # VPN zum Router Filiale 1 'externalViaVPN' { if ($IPsubStringBranch1 -eq $IPSubStringClient) { $Return = -1 } break } # Generell alle erlauben die im eingenen Netzwerk sind Default { if ($IPsubStringBranches -eq $IPSubStringClient) { $Return = -1 #$Return = 0 } break } } $Return } Function GET-IPFromDynDns() { <# .SYNOPSIS Returns the IP-Adress of a given DynDNS entry. Löst die IP-Adresse aus einem DynDns-Namen auf .EXAMPLE GET-IPFromDynDns 'mypersonal.dyndns.org' .EXAMPLE GET-RDPSessionId -UserName johndoe .OUTPUTS System.String (IP-Adress) #> [CmdletBinding()] Param ( [Parameter(Mandatory=$false,Position=0)] [string]$DynDNS ) $PingResult = Test-Connection -ComputerName $DynDNS -Count 1 #$return = $PingResult.ProtocolAddress $return = $PingResult.IPV4Address.IPAddressToString $return } # für ein Logoff wird die RDP-SessionID benötigt function GET-RDPSessionId { <# .SYNOPSIS Returns the RDS session ID of a given user. .DESCRIPTION Leverages query.exe session in order to get the given user's session ID. .EXAMPLE GET-RDPSessionId .EXAMPLE GET-RDPSessionId -UserName johndoe .OUTPUTS System.String #> [CmdletBinding()] Param ( # Identifies a user name (default: current user) [Parameter(ValueFromPipeline = $true)] [System.String]$UserName = $env:USERNAME ) $returnValue = $null try { $ErrorActionPreference = 'Stop' $output = query.exe session $UserName | ForEach-Object {$_.Trim() -replace '\s+', ','} | ConvertFrom-Csv $returnValue = $output.ID } catch { $_.Exception | Write-Error } New-Object psobject $returnValue } # Versendet eine Info-Email. function sendEmail { <# .SYNOPSIS Returns the RDS session ID of a given user. .DESCRIPTION Sends an Email using global variables .EXAMPLE sendEmail .OUTPUTS System.String #> # Check Port $implicitSSL = $true $enableSSL = $true switch ($SMTPServerPort) { 25 {$enableSSL = $false;break} # default Port (unsecure, almost depreached) 465 {break} # connection buildup is secure 8465 {break} 587 {$implicitSSL = $false;break} # connection buildup is not secure 2525 {$implicitSSL = $false;break} 8025 {$implicitSSL = $false;break} } # Ckeck Attachment if ($AttachmentPath -ne '') { if (Test-Path $AttachmentPath) { } else { #Write-Host 'Anhang wurde nicht gefunden ' + $AttachmentPath $AttachmentPath = '' } } If (Test-Path $SMTPPassword) { $SMTPPassword = GET-Content $SMTPPassword | ConvertTo-SecureString } Else { $SMTPPassword = $SMTPPassword | ConvertTo-SecureString -asPlainText -Force } $credentials = New-Object System.Management.Automation.PSCredential($SMTPUser, $SMTPPassword) if ([string]::IsNullOrEmpty($SMTPPassword)) { #if ($SMTPPassword -eq '') { "Mail cound't be sent. Missing Password" } elseif ($implicitSSL) { sentViaWebMail } else { sentViaSmtpClient } } function sentViaSmtpClient { # Set up server connection $smtpClient = New-Object System.Net.Mail.SmtpClient $SMTPServer, $SMTPServerPort $smtpClient.EnableSsl = $enableSSL $smtpClient.Timeout = 5000 # timeout in milliseconds $smtpClient.UseDefaultCredentials = $false; $smtpClient.Credentials = $credentials # Create mail message $message = New-Object System.Net.Mail.MailMessage $From, $To, $subject, $Body $message.IsBodyHTML = $BodyIsHTML if ($AttachmentPath -ne '') { $attachment = New-Object System.Net.Mail.Attachment $AttachmentPath $message.Attachments.Add($attachment) } if ($Bcc -ne '') {$message.Bcc.Add($Bcc)} if ($Cc -ne '') {$message.Cc.Add($Cc)} $return = '' # Send the message try { $smtpClient.Send($message) # Write-Output 'Message sent.' } catch { #Write-Error $_ #Write-Output 'Message send failed.' #add-Content $LogFile 'Message send failed.' $return = 'Message send failed.' } } function sentViaWebMail { # Load System.Web assembly [System.Reflection.Assembly]::LoadWithPartialName("System.Web") > $null $schema = 'http://schemas.microsoft.com/cdo/configuration/' $Ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($SMTPPassword) # Convert to plain text $SMTPPlainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($Ptr) # Create a new mail with the appropriate server settigns $mail = New-Object System.Web.Mail.MailMessage $mail.Fields.Add($schema + 'smtpserver', $SMTPServer) if ($enableSSL) { $mail.Fields.Add($schema + 'smtpserverport', $SMTPServerPort) $mail.Fields.Add($schema + 'smtpusessl', $enableSSL) } $mail.Fields.Add($schema + 'sendusername', $credentials.UserName) $mail.Fields.Add($schema + 'sendpassword', $SMTPPlainPassword) $mail.Fields.Add($schema + 'smtpconnectiontimeout', 5) # timeout in seconds # Use network SMTP server... $mail.Fields.Add($schema + 'sendusing', 2) # ... and basic authentication $mail.Fields.Add($schema + 'smtpauthenticate', 1) # Importance of email #$mail.Fields.Add('urn:schemas:httpmail:importance', 1) #0 = Low importance 1 = Normal importance (default) 2 = High importance # Set up the mail message fields $mail.From = $From $mail.To = $To if ($Cc -ne '') {$mail.Cc = $Cc} if ($Bcc -ne '') {$mail.Bcc = $Bcc} $mail.Subject = $subject if ($BodyIsHTML) {$mail.BodyFormat = 'Html'} $mail.Body = $Body if ($AttachmentPath -ne '') { # Convert to full path and attach file to message $AttachmentPath = (get-item $AttachmentPath).FullName $attachment = New-Object System.Web.Mail.MailAttachment $AttachmentPath $mail.Attachments.Add($attachment) > $null } $return = '' # Send the message try { [System.Web.Mail.SmtpMail]::Send($mail) # Write-Output 'Message sent.' } catch { #Write-Error $_ #Write-Output 'Message send failed.' $return = 'Message send failed.' } } function Get-RDSClientName { <# .SYNOPSIS Returns the RDS client name .DESCRIPTION Returns the value of HKCU:\Volatile Environment\<SessionID>\CLIENTNAME .EXAMPLE Get-RDSClientName -SessionId 4 .EXAMPLE Get-RDSClientName -SessionId Get-RDSSessionId .OUTPUTS System.String #> [CmdletBinding()] Param ( # Identifies a RDS session ID # [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [Parameter(Mandatory = $false)] [System.String]$SessionId ) $returnValue = $null $regKey = 'HKCU:\Volatile Environment\{0}' -f $SessionId try { $ErrorActionPreference = 'Stop' $regKeyValues = Get-ItemProperty $regKey $sessionName = $regKeyValues | ForEach-Object {$_.SESSIONNAME} if ($sessionName -ne 'Console') { $returnValue = $regKeyValues | ForEach-Object {$_.CLIENTNAME} } else { # Write-Warning 'Console session' # $returnValue = $env:COMPUTERNAME } } catch { $_.Exception | Write-Error } New-Object psobject $returnValue } # Sie SET-Variable CLIENTNAME ist zum Startzeitpunkt des Scriptes # durch die Aufgabenplanung noch nicht belegt # Erst durch die Autostart-Routine wird der korrekte CLIENTNAME ausgelesen. <# function get_set_content { $filename = $env:APPDATA + "\~$setclientname.txt" if (Test-Path $filename) { $setcontent = GET-Content $filename if ($setcontent -ne '') {$return = $setcontent.Substring(11)} #CLIENTNAME= Remove-Item -Path $filename -Force } $return } #> # in den Autostart-Ordner des Benutzers eine 'snc.bat' erstellen. # Diese wird nach ein paar Sekunden nach dem RDP-Login ausgeführt # und schriebt in eine Text-Datei die SET-Variable CLIENTNAME # Nachdem die Datei ersellt und ausgelesen wurde wird sie wieder gelöscht <# function set_bat_content { Param ( [Parameter(Mandatory=$false,Position=0)] [switch]$start=$true ) $batfile = $env:APPDATA +"\Microsoft\Windows\Start Menu\Programs\Startup\$setclientname.bat" # Autostart-Ordner des jeweiligen Benutzers if (Test-Path $batfile) { if ($start -eq $false){ Remove-Item -Path $batfile -Force } } else { $valu = 'set CLIENTNAME > %APPDATA%\~' + $setclientname + '.txt' set-Content -Path $batfile -value $valu } } #> function Get-ClientUser-Info { # Aus der Liste in loginevents.txt den eigenen Eintrag auslesen Write-Host $clientinfo if (Test-Path $clientinfo) { $clientinfocontent = GET-Content $clientinfo $popupinfo = $clientinfocontent.trim() } else { $popupinfo = '' } if ($popupinfo -ne '') { $wshell = New-Object -ComObject Wscript.Shell -ErrorAction Stop $wshell.Popup($popupinfo, 15, "Wichtiger Hinweis", 48) } } function add_content { if (!(Test-Path $LogFile)) { $LogFileUser = 'Jeder' # $env:USERDOMAIN + '\' + 'TS_User' ' Damit kann jeder in diese Datei schreiben set-Content -Path $LogFile -value 'User|PCName|Clinet-IP|LogOnTime|Action' $aclVZ = get-acl $LogFile $arVZ = new-object system.security.accesscontrol.filesystemaccessrule($LogFileUser,"FullControl","Allow") $aclVZ.SetAccessRule($arVZ) Set-Acl -path $LogFile $aclVZ } add-Content -Path $LogFile -value $info } function set_deactivate_user { set-Content -Path $deactivateuserPath -value $UserName } # --------------------------------------------------------------------- # Programm Teil # --------------------------------------------------------------------- $UserName = $Env:USERNAME.ToLower() $ComputerRame = $Env:COMPUTERNAME $SessionID = GET-RDPSessionId # Aus der Liste in loginevents.txt den eigenen Eintrag auslesen if (Test-Path $loginevents) { $logineventscontent = GET-Content $loginevents foreach ($row in $logineventscontent) { $column = $row.Split($sep) if ($UserName -eq $column[0].ToLower()) { $ClientPCName = $column[1].ToLower() $ClientIP = $column[2] $LogOnTime = $column[3] break } } } else { $ClientPCName = $missing } if ([string]::IsNullOrEmpty($ClientIP)) {$ClientIP = $missing} # Keine IP-Info vorhanden. # Wurde kein gültiger Client-Name in die Loginevents eingetragen. (iPhone, Smartphone) # Muss dieser Nachtäglich durch ein externe .bat im Autostart ausgelesen werden if ($ClientPCName -eq $missing) { # Watezeit um den Client-PC-Name auszulesen (Dauert immer etwas. Wird nur benötigt wenn z.B. Smartphones im Event-Log keinen Namen hinterlassen) $waitforclinetname = 10 $i = 0 #$setclientname = 'scn' #set_bat_content # Nach einer bestimmten Zeit steht er Name in der Set Variablen # set-Content -Path c:\checkrdp\sekunden.txt -value ('SessionID ' + $SessionID) do { Start-Sleep -Seconds 1 #$ClientPCName = get_set_content $ClientPCName = Get-RDSClientName -SessionId $SessionID $i++ # if([string]::IsNullOrEmpty($ClientPCName)) {add-Content -Path c:\checkrdp\sekunden.txt -value 'IsNullOrEmpty' } # if(!([string]::IsNullOrEmpty($ClientPCName))) {add-Content -Path c:\checkrdp\sekunden.txt -value 'NOT IsNullOrEmpty' } # add-Content -Path c:\checkrdp\sekunden.txt -value $ClientPCName # add-Content -Path c:\checkrdp\sekunden.txt -value $i } while ([string]::IsNullOrEmpty($ClientPCName) -and $i -le $waitforclinetname) #set_bat_content $false } $logoff = CheckUser -UserName $UserName -ClientPCName $ClientPCName -ClientIP $ClientIP -Return $logoff if ([string]::IsNullOrEmpty($LogOnTime)) { $LogOnTime = GET-Date $LogOnTime = $LogOnTime.ToString("(dd.MM.yyyy HH:mm:ss)") } $info = (&{If($logoff -eq -1) {'OK'} Elseif ($logoff -eq 0) {'Email'} Elseif ($logoff -eq 1) {'LogOff'} else {'Deaktivate'}}) $Body += 'Benutzer: ' + $UserName + "`nUserdomain: " + $ClientPCName + "`nIP: " + $ClientIP + "`nLoginzeit: " + $LogOnTime + "`nAktion: " + $info $info = $UserName + $sep + $ClientPCName + $sep + $ClientIP + $sep + $LogOnTime + $sep + $info if ($logoff -ge 1) { if ($sendmail) {sendEMail} add_content logoff $sessionId /server:$COMPUTERNAME if ($logoff -gt 1) {set_deactivate_user} } elseif ($logoff -eq 0) { if ($sendmail) {sendEMail} add_content } elseif ($All) { add_content Get-ClientUser-Info } else { #'OK' Get-ClientUser-Info } #if (Test-Path $loginevents) {Remove-Item -Path $loginevents}
deactivateuser.ps1
#-------------------------------------------------------------------------- # Script zum Deaktivieren von Benutzer(n) welche im Script 'checkuser.ps1' als # zu deakivieren eingestuft wurden. Es wurde versucht sich von einer nicht zulässigen # Arbeitsstation (PC, iPad, Smartphone), IP-Adresse eizuloggen. # Version 1.1 # Dieser Script muss mit Administrator-Rechten durchgeführt werden! # Im Pfad des Scripts if ([string]::IsNullOrEmpty($PSCommandPath)) { # Powershell 2.0 $PSCommandPath = $MyInvocation.MyCommand.Definition $PSScriptRoot = Split-Path -Parent -Path $PSCommandPath } # Datei mit den zu deaktivierenden Usern hat den selben Nanen wie diese Script Datei $DeactivateUserFile = $PSCommandPath.Replace('.ps1', '.txt') # Eigener Pfad #$DeactivateUserFile = 'c:\eigenerPfad' + $PSCommandPath.Replace($PSScriptRoot,'').Replace('.ps1', '.txt') # für jeden Benutzer im eingenen Homedrive #$DeactivateUserFile = $env:HOMEDRIVE + $env:HOMEPATH + $PSCommandPath.Replace($PSScriptRoot,'').Replace('.ps1', '.txt') if (Test-Path $DeactivateUserFile) { $DeactivateUserFileContent = GET-Content $DeactivateUserFile foreach ($row in $DeactivateUserFileContent) { $user = $row.trim() # User in einer Domäne deaktivieren $agl = '/c', 'net user', $user, '/domain', '/active:no' Start-Process -FilePath 'cmd' -ArgumentList $agl -WindowStyle Hidden # User ohne Domäne deaktivieren $agl = '/c', 'net user', $user, '/active:no' Start-Process -FilePath 'cmd' -ArgumentList $agl -WindowStyle Hidden } Remove-Item -Path $DeactivateUserFile -Force }
Zusatz-Tool um verschlüsselte Passwort-Datei zu erzeugen (Email)
create_cedentials.ps1
# In diese Datei wird das Passwort für den Emailversand verschlüsselt gespeichert if ([string]::IsNullOrEmpty($PSScriptRoot)) {$PSScriptRoot = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition} # Powershell 2.0 $EMailCredentialFile = $PSScriptRoot + "\email.cred" (GET-Credential -Message "Email-Adresse & Passwort für Info-Email-Versand" -UserName "empfaenger@maildomain").password | ConvertFrom-SecureString > $EMailCredentialFile #PsEcec # In diese Datei wird das Passwort für den Administrator verschlüsselt gespeichert #$AdministratorCredentialFile = $PSScriptRoot + "\admin.cred" #(GET-Credential -Message "Passwort für Administrator: " -UserName ($Env:COMPUTERNAME + "\Administrator")).password | ConvertFrom-SecureString > $AdministratorCredentialFile