-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGenerateTempPassForAdList.ps1
161 lines (145 loc) · 9.89 KB
/
GenerateTempPassForAdList.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# Bitpusher
# \`._,'/
# (_- -_)
# \o/
# The Digital
# Fox
# @VinceVulpes
# https://theTechRelay.com
# https://github.com/bitpusher2k
#
# GenerateTepmPassForAdList.ps1 - By Bitpusher/The Digital Fox
# v1.9 last updated 2025-03-15
# Processes an exported CSV report of AD users containing at least the columns
# "SamAccountName" and "PasswordLastSet". Generate a random temporary password
# for each enabled account, skipping some known administrative accounts that
# need to be reset manually.
# Designed to be used with the GenerateAdUserReport script to facilitate rapid
# review and resetting of AD account passwords during a security incident.
#
# Usage:
# powershell -executionpolicy bypass -f .\GenerateTepmPassForAdList.ps1 -InputPath "Path\to\input\log.csv" -OutputPath "Path\to\output\file.csv"
#
# powershell -executionpolicy bypass -f .\GenerateTepmPassForAdList.ps1
#
# Use with DropShim.bat to allow drag-and-drop processing of CSV file.
#
# Use simplified and minified one-liner versions at the bottom to easily
# copy/paste into console of remote system (remote tools don't generally handle
# input with line breaks well).
#
#comp #ad #security #incident #script #active #directory #samaccountname #password #reset #powershell
#Requires -Version 4
param(
[string]$InputPath = $(Get-ChildItem c:\temp -Filter "*account_report_before_reset*.csv" | Sort-Object -Descending | Select-Object -First 1),
[string]$OutputPath = 'c:\temp\temppasswords.csv',
[string]$NewColumnName = "NewPassword"
)
# Return random words
function Get-RandomWord {
if (!$script:RandomWords) {
# Get random wordlist from online dictionary API
try {
$script:RandomWords = Invoke-RestMethod -Uri 'https://random-word-api.herokuapp.com/word?length=7&number=100' -Method Get
$script:RandomWords += Invoke-RestMethod -Uri 'https://random-word-api.herokuapp.com/word?length=6&number=100' -Method Get
$script:RandomWords += Invoke-RestMethod -Uri 'https://random-word-api.herokuapp.com/word?length=5&number=100' -Method Get
} catch {
# Fallback to random characters if word retrieval fails
$script:RandomWords = [System.Collections.ArrayList]@()
# $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
$characters = 'abcdefghijklmnopqrstuvwxyz'
# These are supposed to be temporary passwords, so as a fallback using two groups of lowercase letters along with the number should be sufficient, and keep it easier to communicate over the phone
for ($i = 0; $i -lt 300; $i++) {
$password = -join ((1..4) | ForEach-Object { $characters[(Get-Random -Minimum 0 -Maximum $characters.Length)] })
$script:RandomWords += $password
}
}
}
try {
$word = $script:RandomWords | Get-Random
return $word
} catch {
$characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
$password = -join ((1..10) | ForEach-Object { $characters[(Get-Random -Minimum 0 -Maximum $characters.Length)] })
return $password
}
}
# Generate a pair of random words (or character blocks), followed by a two-digit random number, hyphen separated (U+002D, Hyphen-Minus)
function Get-RandomPass {
$first = Get-RandomWord
$second = Get-RandomWord
$third = Get-Random -Minimum 10 -Maximum 99
return "$first-$second-$third"
}
# Process CSV file adding column of random passwords
function Add-NewPassToCSV {
param(
[string]$InputPath,
[string]$OutputPath,
[string]$NewColumnName
)
# Check if input file exists
if (-not (Test-Path $InputPath)) {
Write-Output "`nInput file not found: $InputPath"
return
}
try {
# Read the CSV file
Write-Output "`nInput file found. Loading for processing..."
$csv = Import-Csv -Path $InputPath
$csv = $csv | Where-Object { $_.SamAccountName }
Write-Output "$($csv.length) total user accounts."
$csv = $csv | Where-Object { $_.Enabled -eq 'True' }
Write-Output "$($csv.length) enabled user accounts."
# List of account names to SKIP creating a temporary password for and resetting in bulk
$RegexMatchAdmin = '^administrator$|^adsync$|^aad_|^msol_'
$skip = $csv | Where-Object { $_.SamAccountName -match $RegexMatchAdmin } | Select-Object SamAccountName, PasswordLastSet
if ($skip) {
Write-Output "`nFound admin/service accounts which will be SKIPPED - manually update password(s) for $($skip.length) account(s):"
$skip
Write-Output "`n------------------------------"
}
$csv = $csv | Where-Object { $_.SamAccountName -notmatch $RegexMatchAdmin }
# Add the new column with random word pairs
$csv | ForEach-Object {
$_ | Add-Member -NotePropertyName $NewColumnName -NotePropertyValue (Get-RandomPass)
}
# Export to a new CSV file
$csv | Select-Object SamAccountName, NewPassword | Export-Csv -Path $OutputPath -NoTypeInformation
Write-Output "`nSuccessfully processed CSV file. Output saved to: $OutputPath"
Write-Output "`nTemporary password file written."
Write-Output "`nUsernames in file:"
$csv.SamAccountName | Sort-Object
Write-Output "`nLength of file: $($csv.length) user accounts."
Write-Output "`nReview file with: type $OutputPath"
Write-Output "`nRemove a USERNAME from the file with:"
Write-Output "`$csv = Import-Csv -Path '$OutputPath' ; `$csv | Where-Object { $_.SamAccountName -ne 'USERNAME' } | Export-Csv -Path $OutputPath -NoTypeInformation"
Write-Output "`nUpdate account passwords using temporary password file with:"
Write-Output "Import-Csv $OutputPath -Delimiter `",`" | Foreach { `$NewPassword = ConvertTo-SecureString -AsPlainText `$_.NewPassword -Force ; Set-ADAccountPassword -Identity `$_.SAMAccountName -NewPassword `$NewPassword -Reset -PassThru | Set-ADUser -ChangePasswordAtLogon `$false }"
Write-Output "`nSet passwords to require change at login for all accounts in file (note this will have no effect if password is set to never expire for account):"
Write-Output "Import-Csv $OutputPath -Delimiter `",`" | Foreach { -Identity `$_.SAMAccountName Set-ADUser -ChangePasswordAtLogon `$true }"
Write-Output "`nSet password to expire for all accounts in file:"
Write-Output "Import-Csv $OutputPath -Delimiter `",`" | Foreach { Set-ADUser -Identity `$_.SAMAccountName -PasswordNeverExpires `$False }"
Write-Output "`nEnable all accounts in file:"
Write-Output "Import-Csv $OutputPath -Delimiter `",`" | Foreach { Enable-ADAccount -Identity `$_.SAMAccountName }"
Write-Output "`nBe sure to remove temporary password file from server after reset."
Write-Output "Run AD account report again after reset is complete, and review report."
Write-Output "`nBe sure to reset KRBTGT password twice, with resets spaced at least 10 hours apart:"
Write-Output "Get-ADUser `"krbtgt`" -Property Created, PasswordLastSet ; $NewPassword = ConvertTo-SecureString -AsPlainText 'xXxXxXxX' -Force ; Set-ADAccountPassword -Identity `"krbtgt`" -NewPassword $NewPassword -Reset -PassThru ; Get-ADUser `"krbtgt`" -Property Created, PasswordLastSet"
} catch {
Write-Output "`nError processing CSV file: $_"
}
}
Add-NewPassToCSV -InputPath $InputPath -OutputPath $OutputPath -NewColumnName $NewColumnName
# Minified bare-bones one-liner version of script for copy/paste into console (requires internet access for random wordlist retrieval):
$OneLinerWords = @'
$InPath = $(gci c:\temp -filter "*account_report_before_reset*.csv" | sort -descending | select -first 1) ; $OutPath = 'c:\temp\temppasswords.csv' ; $url = 'https://random-word-api.herokuapp.com/word?number=100' ; $words = $(irm -Uri "$url&length=7") + $(irm -Uri "$url&length=6") + $(irm -Uri "$url&length=5") ; function Get-Word { $word = $words | Get-Random ; return $word } ; function Get-Pass { $first = Get-Word ; $second = Get-Word ; $third = Get-Random -Minimum 10 -Maximum 99 ; return "$first-$second-$third" } ; $Regex = '^administrator$|^adsync$|^aad_|^msol_' ; $csv = ipcsv -Path $InPath ; $csv = $csv | ? {$_.SamAccountName -and $_.Enabled -eq 'True' -and $_.SamAccountName -notmatch $Regex} ; $csv | % { $_ | Add-Member -NotePropertyName 'NewPassword' -NotePropertyValue (Get-Pass) } ; $csv | Select SamAccountName, NewPassword | epcsv -Path $OutPath -NTI
'@
# Version using random character passwords (does not require internet access):
$OneLinerChars = @'
$InPath = $(gci c:\temp -filter "*account_report_before_reset*.csv" | sort -descending | select -first 1) ; $OutPath = 'c:\temp\temppasswords.csv' ; $Words = [System.Collections.ArrayList]@() ; $Chars = 'abcdefghijklmnopqrstuvwxyz' ; for ($i = 0; $i -lt 300; $i++) { $password = -join ((1..4) | ForEach-Object { $Chars[(Get-Random -Minimum 0 -Maximum $Chars.Length)] }) ; $Words += $password } ; function Get-Word { $word = $words | Get-Random ; return $word } ; function Get-Pass { $first = Get-Word ; $second = Get-Word ; $third = Get-Random -Minimum 10 -Maximum 99 ; return "$first-$second-$third" } ; $Regex = '^administrator$|^adsync$|^aad_|^msol_' ; $csv = ipcsv -Path $InPath ; $csv = $csv | ? {$_.SamAccountName -and $_.Enabled -eq 'True' -and $_.SamAccountName -notmatch $Regex} ; $csv | % { $_ | Add-Member -NotePropertyName 'NewPassword' -NotePropertyValue (Get-Pass) } ; $csv | Select SamAccountName, NewPassword | epcsv -Path $OutPath -NTI
'@
# Reset AD passwords using generated file:
$OneLinerResetUsingFile = @'
Import-Csv c:\temp\temppasswords.csv -Delimiter "," | Foreach { $NewPassword = ConvertTo-SecureString -AsPlainText $_.NewPassword -Force ; Set-ADAccountPassword -Identity $_.SAMAccountName -NewPassword $NewPassword -Reset -PassThru | Set-ADUser -ChangePasswordAtLogon $false }
'@