Powershell Skript für Kennwortalter

Mildred

Newbie
Registriert
Dez. 2020
Beiträge
7


Hallo,

ich bin neu im Administratorenbereich und erst recht was Skripten mit Powershell betrifft. Nach Einsteigernbüchern und einen Onlinegrundkurd habe ich folgende Aufgabe bekommen:
Eine Info-E-Mail an die Benutzer senden , wenn ihr Kennwort ein Jahr alt geworden ist (ohne es zu ändern). Die E-Mail soll über das alter Informieren und die Benutzer darauf hinweisen, dass das Kennwort bitte zu ändern ist wenn es öffentlich bekannt wurde. Der Kennwortwechsel soll nicht erzwungen werden. Die Info soll immer zum Geburtstag des Kennwortes kommen, also auch bei zwei Jahre, drei Jahre, ...
Das Auslesen und Berechnen des Kennwortalters klappt ganz gut mit folgendem Skript und auch das importieren und exportieren der CSV. Nur das aktualisieren der Daten, wann ein Eintrag dazukommt, bzw. wie lange er schon drin steht, da stehe ich mit meinem Grundwissen vor einem Problem. Hat noch jemand ein Tip für mich. Folgendes Skript habe ich schon:
Code:
#!/usr/bin/env powershell
Import-Module -Name ActiveDirectory

#Variablen
$TodayTime = Get-Date
$Today = Get-Date -UFormat '%d.%m.%Y'
$WarningLevel = 365
$UsersWithOldPasswordFromAD = New-Object  -TypeName System.Collections.ArrayList
$UsersWithOldPasswordFromFile = New-Object -TypeName System.Collections.ArrayList
$FileArray = New-Object -TypeName System.Collections.ArrayList

# PW Versandliste
[String]$StatusFile = '//xx.own/NETLOGON/Passwort.csv'
If (!(Test-Path -Path ('{0}' -f $StatusFile))) {
    New-Item -Path '//xxx.own/NETLOGON' -Name 'Passwort.csv' -ItemType 'file'
}

#alle aktiven AD-User mit E-Mail-Adresse (außer Systembenutzer)
$AllADUsers = Get-ADUser -Filter { Enabled -eq $True -and PasswordNeverExpires -eq $False -and Mail -like '*@xxx.*' } -Properties PasswordLastSet,Mail

#Berechnung des Kennwortalters
ForEach ($ADUser in  $AllADUsers)
{
    $NutzerName = $ADUser.SamAccountName
    $null = $ADUser.Mail
    $PasswordLastSet = $ADUser.PasswordLastSet

    # Wenn Benutzer von Admins in der AD erstellt wurden, und das Initial-Passwort vom Benutzer noch nicht geändert wurde, dann ist PasswordLastSet nicht gesetzt (NULL)
    # In diesem Fall, setzten wir PasswordLastSet auf Heute, damit die Berechnung "0" ergibt
    If(! $PasswordLastSet) {
    $PasswordLastSet = $TodayTime
    }
    $PasswordAge = ($TodayTime-$PasswordLastSet).Days

    if  ($PasswordAge -ge $WarningLevel)
    {
    $UsersWithOldPasswordFromAD += [PSCustomObject]@{
          'sAMAccountName' = $NutzerName
        'EMailVersand' = $Today
    }
    

}
}

 
$UsersWithOldPasswordFromAD | Export-Csv -Path $StatusFile -NoTypeInformation

Import-Csv $StatusFile
$existingEntry = $UserFromCSV.Where({$_.sAMAccountName -eq $ADUser.SamAccountName})

if ($existingEntry.count -gt 0)
    {
       $LastReminderDays = ((Get-Date) - $ADUser.Emailversand).Days
       if ($LastReminderDays -gt 365)
       { $UsersWithOldPasswordFromAD += [PSCustomObject]@{
         'EMailVersand' = Get-Date -UFormat '%d.%m.%Y'
        }
        }
        }
else {$UsersWithOldPasswordFromAD += [PSCustomObject]@{
          'sAMAccountName' = $NutzerName
        
    }
    $UsersWithOldPasswordFromAD | Export-Csv -Path $StatusFile -NoTypeInformation
    }
 
Moin,

ich kenne mich mit Powershell zwar nicht aus, aber zumindest deine Frage nach
Mildred schrieb:
...wie lange er schon drin steht...
er= (der PW-Eintrag?!)

sollte sich doch schon aus deiner $PasswordLastSet = $ADUser.PasswordLastSet Variable ergeben?!
Oder geht es darum seit wann es den User-Account gibt? Wenn ich mich richtig erinnere sollte es da einen "create" Eintrag im AD zu einem userkonto geben, das sollte sich ja "ähnlich" abfragen lassen
 
Da seid ihr aber sehr nett zu euren Anwendern. In anderen Firmen wird knallhart im AD der Haken "Kennwort ändern bei der nächsten Anmeldung" gesetzt und dann darf's krachen.
 
MadMax 21 schrieb:
Moin,

ich kenne mich mit Powershell zwar nicht aus, aber zumindest deine Frage nach

er= (der PW-Eintrag?!)

sollte sich doch schon aus deiner $PasswordLastSet = $ADUser.PasswordLastSet Variable ergeben?!
Oder geht es darum seit wann es den User-Account gibt? Wenn ich mich richtig erinnere sollte es da einen "create" Eintrag im AD zu einem userkonto geben, das sollte sich ja "ähnlich" abfragen lassen
Mit dem Eintrag, wie lange er schon drin steht ist gemeint, wann der User die letzte Mail bekommen hat. Dieses schreibe ich ja in die CSV. Nach einem erneuten Jahr erst soll der User wieder eine Mail bekommen und nicht jeden Tag, da ja das Skript jeden Tag laufen soll.

Wo ich also die Schwierigkeiten habe, wenn ich die CSV wieder einlese, wie ich aus dem String
Code:
$LastReminderDays = ((Get-Date) - $existingEntry[0].EMailversand).Days
ein Datum draus mache, mit dem ich rechnen kann
Ergänzung ()

Art Vandelay schrieb:
Da seid ihr aber sehr nett zu euren Anwendern. In anderen Firmen wird knallhart im AD der Haken "Kennwort ändern bei der nächsten Anmeldung" gesetzt und dann darf's krachen.
Es gibt ein neues BSI-IT-Grundschutz Kompendium, wo keine Passwörter mehr ablaufen sollten, die Kollegen jedes Jahr es doch ändern sollten laut meiner GF. Und daran soll ich sie per Mail erinnern. Nur habe ich mir das leichter vorgestellt als getan, vor allem als PowerShell Neuling
 
Viel schwieriger stelle ich mir die Erfüllung dieses Teils der Anforderung vor:

"... die Benutzer darauf hinweisen, dass das Kennwort bitte zu ändern ist, wenn es öffentlich bekannt wurde."

Habt ihr da ein schwarzes Brett in der Firma, auf dem sämtliche öffentlich bekannten Passwörter der Benutzer aufgelistet sind?
 
r0b0t schrieb:
Viel schwieriger stelle ich mir die Erfüllung dieses Teils der Anforderung vor:

"... die Benutzer darauf hinweisen, dass das Kennwort bitte zu ändern ist, wenn es öffentlich bekannt wurde."

Habt ihr da ein schwarzes Brett in der Firma, auf dem sämtliche öffentlich bekannten Passwörter der Benutzer aufgelistet sind?
Nein ein schwarzes Brett haben wir nicht, aber die MA sind soweit sensibilisiert, dass wenn der Verdacht des Mißbrauchs oder dass sie bekannt sind, sofort zu ändern sind.
 
Hey, kenne mich mit AD nicht aus, wäre es aber nicht einfacher den Usern ein custom Attribut zu geben - wie z.B. WarningLastSent?

Falls du wirklich über die CSV gehen willst, wirst du nicht drum herum kommen deine einfache List passend zum aktuellen User zu durchsuchen.
 
Zunächst: Iiih! Initialpaßwörter gelten also unendlich? Nicht gut! Da müßt ihr euch was einfallen lassen. Sonst sind am Ende die das Einfallstor.
Vielleicht hab ich aber auch was falsch interpretiert, dann sorry.
In jedem Fall, auch Initial-PWs müssen mitgeteilt werden.

Was den Anspruch angeht, ob PW geschickt oder nicht... könnte man theoretisch zweistufig rangehen.

Ad 1, wenn ein PW-Eintrag gefunden wird, der heute "Geburtstag" hat, kriegt er in einer eigenen Spalte in der CSV ein Gesendet=$True exakt dann, wenn die Mail verschickt wurde (und der erfolgreiche Versand bestätigt wurde).

Ad 2, alle PW-Einträge die NICHT "heute" Geburtstag haben und die aber ein Gesendet=$True konfiguriert haben, bekommen A das Alter um eins hochgezählt und B das $Gesendet auf $False zurückgesetzt.


WENN die Daten aber aufgehoben werden müssen, wenn also 2025 noch ersichtlich sein muß, welcher MA wann informiert wurde wg PW-Wechsel (an der Stelle an den vier Terminen bis dahin) dann muß eine weitere Tabelle geführt werden mit sAMAccountName des MA und jeweils dem Sendedatum zugeordnet. Das kann man einfach seriell wegschreiben.

PS, wenn datetime hinterher noch auswertbar sein soll, dann haben alle datetime Objekte ein .Ticks Attribut (Typ long) das man protokollieren kann. Das beschreibt den Zeitstempel exakt, ähnlich wie unix' timestamp, auch wenn nicht dazu kompatibel.

Dann reicht
PowerShell:
[long] $Timestamp = $CsvZeile.Zeitstempel
[datetime] $Wann = $Timestamp
per extensivem Gebrauch von Powershell Auto-Typ-Konversion.
 
Zunächst: Iiih! Initialpaßwörter gelten also unendlich? Nicht gut! Da müßt ihr euch was einfallen lassen. Sonst sind am Ende die das Einfallstor.
Vielleicht hab ich aber auch was falsch interpretiert, dann sorry.
In jedem Fall, auch Initial-PWs müssen mitgeteilt werden.
Das Startkennwort müssen die User selbstverständlich bei der ersten Anmeldung ändern.

Ad 1, wenn ein PW-Eintrag gefunden wird, der heute "Geburtstag" hat, kriegt er in einer eigenen Spalte in der CSV ein Gesendet=$True exakt dann, wenn die Mail verschickt wurde (und der erfolgreiche Versand bestätigt wurde).

Ad 2, alle PW-Einträge die NICHT "heute" Geburtstag haben und die aber ein Gesendet=$True konfiguriert haben, bekommen A das Alter um eins hochgezählt und B das $Gesendet auf $False zurückgesetzt
Danke für diesen Ansatz. Da muss ich mal basteln, wie ich im Skript umgesetzt bekomme.
 
Mildred schrieb:
Das Startkennwort müssen die User selbstverständlich bei der ersten Anmeldung ändern.
Soweit so klar. Und wenn sie sich nicht oder erst spät erstmalig anmelden? Wenn es Konten für Benutzer gibt, die nie kommen?

Für konten mit mail und nur mit initial pw muß der jeweilige Benutzer auch informiert werden. eben weil es kein bekanntes PWSet Datum gibt.

Oder ihr deaktiviert diese Konten nach Zeitraum X wieder. Ggfs absprechen.
 
Wenn Benutzer nie kommen deaktivieren wir wieder die Konten. Nutzer mit Initialpasswort setze ich ja ein PWSet Datum mit:
Code:
 # Wenn Benutzer von Admins in der AD erstellt wurden, und das Initial-Passwort vom Benutzer noch nicht geändert wurde, dann ist PasswordLastSet nicht gesetzt (NULL)
    # In diesem Fall, setzten wir PasswordLastSet auf Heute, damit die Berechnung "0" ergibt
    If(! $PasswordLastSet) {
    $PasswordLastSet = $TodayTime
    }
    $PasswordAge = ($TodayTime-$PasswordLastSet).Days

Und somit fallen sie aus der Mailbenachrichtung raus.

RalphS schrieb:
Ad 1, wenn ein PW-Eintrag gefunden wird, der heute "Geburtstag" hat, kriegt er in einer eigenen Spalte in der CSV ein Gesendet=$True exakt dann, wenn die Mail verschickt wurde (und der erfolgreiche Versand bestätigt wurde).

Ad 2, alle PW-Einträge die NICHT "heute" Geburtstag haben und die aber ein Gesendet=$True konfiguriert haben, bekommen A das Alter um eins hochgezählt und B das $Gesendet auf $False zurückgesetzt.


WENN die Daten aber aufgehoben werden müssen, wenn also 2025 noch ersichtlich sein muß, welcher MA wann informiert wurde wg PW-Wechsel (an der Stelle an den vier Terminen bis dahin) dann muß eine weitere Tabelle geführt werden mit sAMAccountName des MA und jeweils dem Sendedatum zugeordnet. Das kann man einfach seriell wegschreiben.
Ich sitze jetzt seit Stunden und bekomm mit meinem Grundwissen nicht gebacken
 
Genau, sie fallen aus der Benachrichtigung raus. Das hatte ich beanstandet - denn wenn ein Benutzer (aus Fleisch und Blut also) vergißt daß da ein Konto auf ihn wartet, vor allem wenn das nicht sein einziges/erstes war....

Wenn Du natürlich sagst, das wird irgendwann wieder deaktiviert, dann paßt das vermutlich. Ansonsten wäre mein Vorschlag gewesen, solche ausstehenden Paßwörter genauso an die Benutzer zu melden.

Bzgl der Umsetzung ist halt zunächst die Frage was Du/ihr brauchst. Reicht es, wenn die Anwender turnusgemäß informiert werden und das in etwa sichergestellt ist, oder muß es auch rekonstruierbar sein --- wenn jetzt ein Anwender kommt "hä, ich hab so ne Mail noch nie gesehn, was wollt ihr von mir?!" müßt ihr dann in der Lage sein "doch, da, guckst du hier, das haben wir dir dann-und-dann geschickt und zwar da und dort hin"?


Auch grad nicht ganz sicher, warum Du die CSV-Daten zweimal schreibst. Einmal sollte vollends genügen und --- wenn irgend möglich --- irgendwohin wo weder sysvol noch netlogon dabei ist.

Und dann guckst Du grad alle Benutzer auf einmal an. Das ist sehr wahrscheinlich unnütz. Das Problem ist ja ein benutzer-lokales: es betrifft jeden einzelnen. Also bietet sich zunächst mal ein Anmeldescript an, was ausgeführt wird, wenn der jeweilige User kommt.



In jedem Fall, unabhängig davon ob in einer Schleife oder im Benutzerkontext selber: das Script ist erstmal per Designation täglich auszuführen. Schließlich kann jedes PW an "diesem" Tag Geburtstag haben. Da wäre es doof, wenn dieser fragliche Tag dieses Jahr ausfällt und die Meldung erst eins später kommt.

.PasswordLastSet ist bereits ein Datetime Objekt. Also genügt es, die Eigenschaften Day und Month von [datetime]::Today einerseits und .Passwordlastset andererseits zu vergleichen. Sind beide gleich, hat das PW Geburtstag und wir müssen etwas tun.


Jetzt brauchen wir eine CSV in irgendeiner Form mit den Einträgen für sAMAccountName und MailVerschickt. Dabei wäre MailVerschickt die Angabe des jeweiligen Jahres, wo die Mail verschickt wurde. Einfach die Jahreszahl, zB 2021. Mehr sollte nicht erforderlich sein, und das beste, man kann einfach wegschreiben.


Jetzt muß man sinngemäß nur noch gucken:
PowerShell:
$haveMatch = Import-CSV <csvfile> | Where-Object { $_.Mailverschickt -eq (get-date).Year  -and $_.sAMAccountName -eq $AdUser.sAMAccountName}

if($haveMatch) {
<# Match für Person und Jahr gefunden => Mail ist raus #> 
'Tu nichts'
}
else
 { <# Mail ist noch nicht raus, also eine schicken #>
Send-Mailmessage wasauchimmer
# Und nun die CSV um den Eintrag ergänzen
# An der Stelle muß klar sein wie die CSV-Datei aussieht. Es sind ja grad nur zwei Spalten, also vergessen wir alles andere und schreiben das plaintext. Dazu müssen wir natürlich den Delimiter kennen und die Spaltenordnung. 
# Daher hier nur beispielhaft.
Add-Content -Path <pfad zur CSV> -Value ( '{0};{1}' -f $aduser.samaccountname, [datetime]::today.Year )
}

Fertig. Bis auf Spezifika ist nichts weiter zu machen.
 
  • Bei Jahren in Tagen bitte ans Schaltjahr denken
  • Datumsangaben in ISO speichern
  • Für's Suchen ($KnownUsers) assoziative Arrays, Hashtables etc. nutzen
  • Die Lösung mit der CSV finde ich immer noch riskant
  • Ich kenne mich mit AD nicht aus. Ist der SamAccountName immer unique?
PowerShell:
Param (
    # Reminder Interval in Days
    [INT] $RID = $( If ( [System.DateTime]::IsLeapYear( ( Get-Date ).Year ) ) { 366 } Else { 365 } ), # Respekt dem Schaltjahr!
    [STRING] $Path = '.\userpwre.csv'
)

Update-TypeData -TypeName 'System.DateTime' -MemberType ScriptMethod -MemberName 'ToISOString' -Value { # Script läuft ohne -Force
    Param (
        [STRING] $ISO = '8601'
    )

    $This | Get-ISODate $ISO
}

Function Get-ISODate {
    [CmdletBinding ( DefaultParameterSetName = 'DateTime' )]
    Param (
        [Parameter ( Position = 0, ParameterSetName = 'DateTime' )]
        [Parameter ( Position = 0, ParameterSetName = 'Override' )]
        [STRING] $ISO = '8601',

        [Parameter ( ParameterSetName = 'DateTime', ValueFromPipeline )]
        [DateTime] $Date = ( Get-Date ),

        [Parameter ( ParameterSetName = 'Override' )]
        [INT] $Year = ( Get-Date ).Year,
        [Parameter ( ParameterSetName = 'Override' )]
        [INT] $Month = ( Get-Date ).Month,
        [Parameter ( ParameterSetName = 'Override' )]
        [INT] $Day = ( Get-Date ).Day
    )

    Begin {
        Switch ( $ISO ) { # Für andere/ältere ISO-Standards, die noch zu implementieren wären. :p
            Default { # ISO 8601
                $Format = 'yyyy-MM-dd'
                $UFormat = '%Y-%m-%d' # PS7+: %F
            }
        }
    }

    Process {
        Switch ( $PSCmdlet.ParameterSetName ) {
            'DateTime' { $Date.ToString( $Format ) }
            'Override' { Get-Date -Year $Year -Month $Month -Day $Day -UFormat $UFormat }
        }
    }

    End {
    }
}

# Dummy Users from AD (einfacher Array)
[PSCustomObject[]] $ADUsers = @(
    [PSCustomObject] @{
        'SamAccountName' = 'ACC01'
        'PasswordLastSet' = ( Get-Date ).AddDays( -30 ) | Get-ISODate
        'PasswordNeverExpires' = $TRUE
        'eMailAddress' = 'herbert@xxx.de'
    }
    [PSCustomObject] @{
        'SamAccountName' = 'ACC02'
        'PasswordLastSet' = ( Get-Date ).AddDays( -60 ) | Get-ISODate
        'PasswordNeverExpires' = $TRUE
        'eMailAddress' = 'maria@xxx.de'
    }
    [PSCustomObject] @{
        'SamAccountName' = 'ACC03'
        'PasswordLastSet' = ( Get-Date ).AddDays( -930 ) | Get-ISODate
        'PasswordNeverExpires' = $TRUE
        'eMailAddress' = 'sophie@xxx.de'
    }
    [PSCustomObject] @{
        'SamAccountName' = 'ACC04'
        'PasswordLastSet' = ( Get-Date ).AddDays( -960 ) | Get-ISODate
        'PasswordNeverExpires' = $TRUE
        'eMailAddress' = 'gebieter@xxx.de'
    }
    [PSCustomObject] @{
        'SamAccountName' = 'ACC05'
        'PasswordLastSet' = ( Get-Date ).AddDays( -990 ) | Get-ISODate
        'PasswordNeverExpires' = $TRUE
        'eMailAddress' = 'horst@xxx.de'
    }
    [PSCustomObject] @{
        'SamAccountName' = 'ACC06'
        'PasswordLastSet' = ( Get-Date ).AddDays( -$RID ) | Get-ISODate # Happy Birthday!
        'PasswordNeverExpires' = $TRUE
        'eMailAddress' = 'thomas@xxx.de'
    }
)

$ErrorActionPreference = 'Stop'

# Dummy Users from CSV (assoziativer Array)
Try { # Gerade bei Remote-Pfaden iMMER mit Try/Catch arbeiten!
    $UsersCSV = Import-CSV $Path
    $KnownUsers = @{}
    $UsersCSV | %{ # ForEach-Alias
        $KnownUsers[$_.SamAccountName] = [PSCustomObject] @{
            'ReminderLastSent' = $_.ReminderLastSent
            'ReminderToSend' = $FALSE
            'eMailAddress' = $_.eMailAddress
        }
    }
}
Catch [System.IO.FileNotFoundException] {
    If ( $TRUE ) { # Fieser Trick! Eigentlich käme hier eine Abfrage für entsprechenden Abfang-Code rein!
        $KnownUsers = @{
            'ACC03' = [PSCustomObject] @{
                'ReminderLastSent' = ( Get-Date ).AddDays( -30 ) | Get-ISODate
                'ReminderToSend' = $FALSE
                'eMailAddress' = 'sophie@xxx.de'
            }
            'ACC04' = [PSCustomObject] @{
                'ReminderLastSent' = ( Get-Date ).AddDays( -430 ) | Get-ISODate
                'ReminderToSend' = $FALSE
                'eMailAddress' = 'gebieter@xxx.de'
            }
            'ACC05' = [PSCustomObject] @{
                'ReminderLastSent' = ( Get-Date ).AddDays( -460 ) | Get-ISODate
                'ReminderToSend' = $FALSE
                'eMailAddress' = 'horst@xxx.de'
            }
        }
    }
    Else {
        Throw
    }
}

# Durchlauf der AD-User(s)
$Birthday = ( Get-Date ).AddDays( -$RID )
$ADUsers | %{ # ForEach-Alias
    $CUN = $_.SamAccountName # Current UserName
    If ( ( Get-Date $_.PasswordLastSet ) -le $Birthday ) { # PasswordLastSet von ISO-Date-String in DateTime-Ojekt umwandeln
        If ( $KnownUsers[$CUN] ) { # Überprüfen, ob Nutzer bereits bekannt, ...
            If ( ( Get-Date $KnownUsers[$CUN].ReminderLastSent ) -le $Birthday ) { # ReminderLastSent von ISO-Date-String in DateTime-Ojekt umwandeln
                $KnownUsers[$CUN].ReminderToSend = $TRUE # Oder gesonderte Liste erstellen?
                $KnownUsers[$CUN].eMailAddress = $_.eMailAddress # E-Mail-Adresse akutalisieren
            }
        }
        Else { # ... anlegen falls nicht
            $KnownUsers[$CUN] = [PSCustomObject] @{
                'ReminderLastSent' = $_.PasswordLastSet # PW-Set-Datum damit E-Mail-Versand-Fehler abgefangen werden können
                'ReminderToSend' = $TRUE # Oder gesonderte Liste erstellen?
                'eMailAddress' = $_.eMailAddress
            }
        }
    }
}

$TodayISO = Get-ISODate
$KnownUsers.Keys | %{ # ForEach-Alias
    If ( $KnownUsers[$_].ReminderToSend ) {
        Try {
            # E-Mail-Versand
            $KnownUsers[$_].ReminderLastSent = $TodayISO
        }
        Catch {
        }
    }
}

# Umwandlung in einfachen Array für Export-CSV (nötig?)
$UsersCSV = [PSCustomObject[]] @()
$KnownUsers.Keys | %{ # ForEach-Alias
    $UsersCSV += [PSCustomObject] @{
        'SamAccountName' = $_
        'ReminderLastSent' = $KnownUsers[$_].ReminderLastSent
        'eMailAddress' = $KnownUsers[$_].eMailAddress
    }
}

Try { # Und wieder Try/Catch!
    $UsersCSV | Export-CSV -Path $Path -NoTypeInformation
}
Catch {
    Throw
}
 
Zuletzt bearbeitet: (Code-Fixes)
  • Gefällt mir
Reaktionen: DubZ
Ja, samaccountname ist unique bezogen auf eine domain - Achtung, nicht auf den Forest.

Try Catch erfordert in ps zwingend ein ErrorAction Stop. Sonst tut das gar nichts.
 
  • Gefällt mir
Reaktionen: floTTes
Zurück
Oben