a1a17e81a1
Includes updated JS challenge scripts with Claude-User whitelist, same-site referer bypass, Blackbox-Exporter allowed bot, and all new exporters, cheat sheets, and automation scripts.
311 lines
11 KiB
PowerShell
311 lines
11 KiB
PowerShell
<#
|
|
.SYNOPSIS
|
|
Checks password expiration for both AD and local Windows accounts.
|
|
.DESCRIPTION
|
|
Auto-detects whether the current user is a domain or local account,
|
|
queries the appropriate source for password expiration, and shows
|
|
a notification if the password expires within the warning threshold.
|
|
Can be scheduled via Task Scheduler to run at logon or daily.
|
|
.PARAMETER WarningDays
|
|
Number of days before expiration to start warning. Default: 14
|
|
.PARAMETER User
|
|
Check a specific user instead of the current user.
|
|
.PARAMETER Quiet
|
|
Suppress output unless password is within the warning window.
|
|
.PARAMETER Test
|
|
Force notification even if expiry is not near.
|
|
.PARAMETER Help
|
|
Show usage information.
|
|
.NOTES
|
|
Author: Phil Connor <contact@mylinux.work>
|
|
License: MIT (https://opensource.org/licenses/MIT)
|
|
Version: 1.02
|
|
#>
|
|
|
|
param(
|
|
[int]$WarningDays = 14,
|
|
[string]$User = "",
|
|
[switch]$Quiet,
|
|
[switch]$Test,
|
|
[switch]$InstallScheduledTask,
|
|
[int]$TaskIntervalMinutes = 60,
|
|
[Alias("h")]
|
|
[switch]$Help
|
|
)
|
|
|
|
if ($Help) {
|
|
Write-Host @"
|
|
Usage: .\password-expiry-check.ps1 [OPTIONS]
|
|
|
|
Check password expiration for domain (AD) and local Windows accounts.
|
|
Auto-detects account type. Shows desktop popup when expiry is near.
|
|
|
|
Options:
|
|
-WarningDays N Warning threshold in days (default: 14)
|
|
-User USERNAME Check a specific user (default: current user)
|
|
-Quiet Suppress output unless password is within warning window
|
|
-Test Force notification even if expiry is not near
|
|
-InstallScheduledTask Create a scheduled task for recurring checks
|
|
-TaskIntervalMinutes N Interval for scheduled task (default: 60)
|
|
-Help Show this help
|
|
|
|
Examples:
|
|
.\password-expiry-check.ps1 # check current user
|
|
.\password-expiry-check.ps1 -WarningDays 30 # warn within 30 days
|
|
.\password-expiry-check.ps1 -User jdoe # check specific user
|
|
.\password-expiry-check.ps1 -Test # force notification
|
|
.\password-expiry-check.ps1 -Quiet # silent unless near expiry
|
|
.\password-expiry-check.ps1 -InstallScheduledTask # create scheduled task
|
|
"@
|
|
exit 0
|
|
}
|
|
|
|
# Determine which username to check
|
|
$CheckUsername = if ($User -ne "") { $User } else { $env:USERNAME }
|
|
|
|
if ($InstallScheduledTask) {
|
|
$taskName = "PasswordExpiryCheck"
|
|
$existingTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
|
|
|
|
if (-not $existingTask) {
|
|
$taskAction = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$($MyInvocation.MyCommand.Path)`" -Quiet"
|
|
|
|
if (-not $TaskIntervalMinutes -or $TaskIntervalMinutes -le 0) {
|
|
throw "TaskIntervalMinutes must be a positive integer"
|
|
}
|
|
|
|
$taskTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) -RepetitionInterval (New-TimeSpan -Minutes $TaskIntervalMinutes) -RepetitionDuration (New-TimeSpan -Days 365)
|
|
$taskPrincipal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
|
|
|
|
try {
|
|
Write-Host "Creating scheduled task: $taskName"
|
|
Register-ScheduledTask -TaskName $taskName -Action $taskAction -Trigger $taskTrigger -Principal $taskPrincipal -Description "Checks password expiry every $TaskIntervalMinutes minutes"
|
|
|
|
$createdTask = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
|
|
if (-not $createdTask) {
|
|
throw "Failed to verify scheduled task creation"
|
|
}
|
|
Write-Host "Successfully created scheduled task: $taskName" -ForegroundColor Green
|
|
} catch {
|
|
Write-Error "Failed to create auto-start task: $($_.Exception.Message)"
|
|
throw
|
|
}
|
|
} else {
|
|
Write-Host "Scheduled task '$taskName' already exists, skipping creation"
|
|
}
|
|
}
|
|
|
|
function Get-AccountType {
|
|
$computerSystem = Get-WmiObject Win32_ComputerSystem
|
|
$isDomainJoined = $computerSystem.PartOfDomain
|
|
|
|
if ($isDomainJoined -and ($env:USERDOMAIN -ne $env:COMPUTERNAME)) {
|
|
return "Domain"
|
|
}
|
|
return "Local"
|
|
}
|
|
|
|
function Get-ADPasswordExpiry {
|
|
param([string]$Username)
|
|
|
|
try {
|
|
# Method 1: net user /domain (works without RSAT)
|
|
$netUser = net user $Username /domain 2>&1
|
|
if ($LASTEXITCODE -eq 0) {
|
|
$expiryLine = $netUser | Select-String "Password expires"
|
|
if ($expiryLine) {
|
|
$dateStr = ($expiryLine -split "\s{2,}")[1].Trim()
|
|
if ($dateStr -ne "Never") {
|
|
return [datetime]::Parse($dateStr)
|
|
}
|
|
return "Never"
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
Write-Warning "net user /domain method failed: $_"
|
|
}
|
|
|
|
try {
|
|
# Method 2: ADSI (no RSAT required)
|
|
$searcher = New-Object DirectoryServices.DirectorySearcher
|
|
$searcher.Filter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$Username))"
|
|
$searcher.PropertiesToLoad.AddRange(@("pwdLastSet", "msDS-UserPasswordExpiryTimeComputed"))
|
|
$result = $searcher.FindOne()
|
|
|
|
if ($result) {
|
|
$expiryTicks = $result.Properties["msDS-UserPasswordExpiryTimeComputed"]
|
|
if ($expiryTicks -and $expiryTicks[0] -gt 0 -and $expiryTicks[0] -lt [long]::MaxValue) {
|
|
return [datetime]::FromFileTime($expiryTicks[0])
|
|
}
|
|
return "Never"
|
|
}
|
|
}
|
|
catch {
|
|
Write-Warning "ADSI method failed: $_"
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Get-LocalPasswordExpiry {
|
|
param([string]$Username)
|
|
|
|
# Method 1: Get-LocalUser (available on Windows 10/Server 2016+)
|
|
if (Get-Command Get-LocalUser -ErrorAction SilentlyContinue) {
|
|
try {
|
|
$localUser = Get-LocalUser -Name $Username -ErrorAction Stop
|
|
if ($localUser.PasswordExpires) {
|
|
return $localUser.PasswordExpires
|
|
}
|
|
return "Never"
|
|
}
|
|
catch {
|
|
Write-Warning "Get-LocalUser method failed: $_"
|
|
}
|
|
}
|
|
|
|
# Method 2: net user (fallback)
|
|
try {
|
|
$netUser = net user $Username 2>&1
|
|
if ($LASTEXITCODE -eq 0) {
|
|
$expiryLine = $netUser | Select-String "Password expires"
|
|
if ($expiryLine) {
|
|
$dateStr = ($expiryLine -split "\s{2,}")[1].Trim()
|
|
if ($dateStr -ne "Never") {
|
|
return [datetime]::Parse($dateStr)
|
|
}
|
|
return "Never"
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
Write-Warning "net user method failed: $_"
|
|
}
|
|
|
|
return $null
|
|
}
|
|
|
|
function Show-TerminalBanner {
|
|
param(
|
|
[string]$Title,
|
|
[string]$Message
|
|
)
|
|
|
|
# Word-wrap message to fit within the box (max 42 chars per line)
|
|
$words = $Message -split '\s+'
|
|
$lines = @()
|
|
$currentLine = ""
|
|
foreach ($word in $words) {
|
|
if ($currentLine.Length -eq 0) {
|
|
$currentLine = $word
|
|
} elseif (($currentLine.Length + 1 + $word.Length) -le 42) {
|
|
$currentLine += " $word"
|
|
} else {
|
|
$lines += $currentLine
|
|
$currentLine = $word
|
|
}
|
|
}
|
|
if ($currentLine.Length -gt 0) { $lines += $currentLine }
|
|
|
|
Write-Host ""
|
|
Write-Host ([char]0x2554 + ([string][char]0x2550 * 46) + [char]0x2557)
|
|
Write-Host ([char]0x2551 + (" " * 46) + [char]0x2551)
|
|
Write-Host ([char]0x2551 + " " + $Title.PadRight(42) + " " + [char]0x2551)
|
|
Write-Host ([char]0x2551 + ([string][char]0x2500 * 46) + [char]0x2551)
|
|
foreach ($line in $lines) {
|
|
Write-Host ([char]0x2551 + " " + $line.PadRight(42) + " " + [char]0x2551)
|
|
}
|
|
Write-Host ([char]0x2551 + (" " * 46) + [char]0x2551)
|
|
Write-Host ([char]0x255A + ([string][char]0x2550 * 46) + [char]0x255D)
|
|
Write-Host ""
|
|
}
|
|
|
|
function Show-MessageBoxNotification {
|
|
param(
|
|
[string]$Title,
|
|
[string]$Message
|
|
)
|
|
|
|
# Terminal banner (always shown)
|
|
Show-TerminalBanner -Title $Title -Message $Message
|
|
|
|
# Desktop dialog - requires user to click OK to dismiss
|
|
try {
|
|
Add-Type -AssemblyName System.Windows.Forms -ErrorAction Stop
|
|
[System.Windows.Forms.MessageBox]::Show(
|
|
$Message,
|
|
$Title,
|
|
[System.Windows.Forms.MessageBoxButtons]::OK,
|
|
[System.Windows.Forms.MessageBoxIcon]::Warning
|
|
) | Out-Null
|
|
} catch {
|
|
# Non-interactive session (Server Core, SSH, etc.) - banner is sufficient
|
|
}
|
|
}
|
|
|
|
# --- Main ---
|
|
|
|
$accountType = Get-AccountType
|
|
|
|
if ($accountType -eq "Domain") {
|
|
$expiryDate = Get-ADPasswordExpiry -Username $CheckUsername
|
|
}
|
|
else {
|
|
$expiryDate = Get-LocalPasswordExpiry -Username $CheckUsername
|
|
}
|
|
|
|
$displayUser = if ($User -ne "") { $User } else { "$env:USERDOMAIN\$env:USERNAME" }
|
|
|
|
if ($expiryDate -eq "Never") {
|
|
if (-not $Quiet) {
|
|
Write-Host "Account Type: $accountType"
|
|
Write-Host "User: $displayUser"
|
|
Write-Host "Password is set to never expire."
|
|
}
|
|
if ($Test) {
|
|
Show-MessageBoxNotification -Title "Password Expiry Test" -Message "Account: $displayUser ($accountType) - password never expires. (-Test mode)"
|
|
}
|
|
exit 0
|
|
}
|
|
|
|
if (-not $expiryDate) {
|
|
if (-not $Quiet) {
|
|
Write-Host "Account Type: $accountType"
|
|
Write-Host "User: $displayUser"
|
|
Write-Host "Could not determine password expiry date."
|
|
}
|
|
exit 1
|
|
}
|
|
|
|
$daysRemaining = ($expiryDate - (Get-Date)).Days
|
|
|
|
# Quiet mode - exit silently if not near expiry
|
|
if ($Quiet -and $daysRemaining -gt $WarningDays -and -not $Test) {
|
|
exit 0
|
|
}
|
|
|
|
Write-Host "Account Type: $accountType"
|
|
Write-Host "User: $displayUser"
|
|
Write-Host "Password Expires: $($expiryDate.ToString('yyyy-MM-dd HH:mm'))"
|
|
Write-Host "Days Remaining: $daysRemaining"
|
|
|
|
if ($daysRemaining -le 0) {
|
|
$title = "PASSWORD EXPIRED"
|
|
$message = "Your password has EXPIRED as of $($expiryDate.ToString('yyyy-MM-dd')).`nPlease change it immediately (Ctrl+Alt+Del > Change Password)."
|
|
Show-MessageBoxNotification -Title $title -Message $message
|
|
}
|
|
elseif ($daysRemaining -le $WarningDays) {
|
|
$title = "Password Expiring Soon"
|
|
$message = "Your password will expire in $daysRemaining day(s) on $($expiryDate.ToString('yyyy-MM-dd')).`nPlease change it soon (Ctrl+Alt+Del > Change Password)."
|
|
Show-MessageBoxNotification -Title $title -Message $message
|
|
}
|
|
elseif ($Test) {
|
|
$title = "Password Expiry Test"
|
|
$message = "Your password will expire in $daysRemaining day(s) on $($expiryDate.ToString('yyyy-MM-dd')). (-Test mode)"
|
|
Show-MessageBoxNotification -Title $title -Message $message
|
|
}
|
|
else {
|
|
Write-Host "Password expiry is more than $WarningDays days away. No notification needed."
|
|
}
|