Blue Flower

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).

Richtlinien für Lokaler Computer > Computerconfiguration > Windows-Einstellungen > Sicherheitseinstellungen > Lokale Richtlinien > Überwachungsrichtlinie => Anmeldeereignisse überwachen : Diese Versucht überwachen Erfolgreich aktivieren lokale gruppenreichtline anmeldeereiginsse ueberwachen 
Sicherheitseinstellungen > Erweiterte Überwachungsrichtlinien > Systemüberwachungsrichtlinien - Lokales Gruppenrichtlinienobjekt > Anmelden/Abmelden -> Anmelden überwachen => folgende Überwachungsereiginsse konfigurieren Erfolg aktivieren.  



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.
benutzerkontnsteuerung

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
    - Aufgabenplanung starten (taskschd.msc) und im Menü > Aktion > Aufgabe erstellen aufrufen
      * Register Allgemein:
        - Als erstes in den Sicherheitsoptionen die Benutzer-Gruppe auswählen aus dem AD oder lokal deren Gruppe verwendet werden soll.
        - Nur ausführen wenn der Benutzer angemeldet ist
        - Ausgeblendet anklicken
      * Register Trigger:
         Die beiden Aktionen auswählen und beide male "Jeder Benutzer" auswählen.
         - "Bei Anmeldung"
         - "Bei Verbindung mit Benutzersitzung" (Relogin)
      * Register Aktionen:
        Hier wird das "Startskript" eingetragen welche unter A beschrieben ist und die weiteren Aktionen B und C verdeckt ausführt. 
        - Programm Script: cscript.exe
        - Parameter: /nologo c:\checkrdp\run_initila.vbs (absoluter Skriptpfad)
        bzw.
        - Programm Script: powershell.exe
        - Parameter: -nologo c:\checkrdp\run_initila.ps1 (absoluter Skriptpfad)
      * Register Bedingungen: keine Auswahl
      * Register Einstellungen:
        - "Ausführen der Aufgabe bei Bedarf zulassen" auswählen
        - "Aufgabe beenden, falls Ausführung länger als 1 Stunde" aktivieren

 => Aufgabe speichern
Bei der nächsten Anmeldung per RDP wird jetzt der Benutzer überprüft

  aufgabenplanung rdp check allgemein aufgabenplanung rdp check trigger
aufgabenplanung rdp check trigger 2 aufgabenplanung rdp check aktion
aufgabenplanung rdp check bedingungen
aufgabenplanung rdp check einstellungen


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 &gt; 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 &gt; 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 -&gt; %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 -&gt; Username
  # 1 -> PC-Name (Client)
  # 2 -> IP-Adresse
  # 3 -&gt; Ameldezeitpunkt
  # 4 -> Art des Logins
  # 5 -&gt; 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 = 'kz1.Mf58iTqs()-y7' # 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") &gt; $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) &gt; $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&gt;\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 &gt; %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  &gt; $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 &gt; $AdministratorCredentialFile