Link to home
Start Free TrialLog in
Avatar of goneal
goneal

asked on

Identify local admin accounts with null passwords using Powershell in a Win XP domain

I manage about 400 machines in a Win XP networked environment and would like to determine which ones have a null password on the local admin account. It's a simple enough matter to use a tool to reset all local admin passwords, but at this point I would like to build a case for changing our policies and would like to assess the extent of the problem first.

There is a similar bit of code posted here on experts exchange which verifies whether or not a certain password has been used, but I'm not sure how to customize for Powershell. The title is Script to Verify Local Admin Password.

The below code gives me a list of machines which I need to assess for null password. Once the list is built, I would like to use Export-CSV to build the report to send my boss, which should include machine name. If there is a password, I don't need to know or change it, I'm just after the nulls/zero length strings or whatever the blank password data type is, and I would prefer to use Powershell.
$strComputer = Read-Host "Enter the first 3 letters of the terminal. For all computers, just press enter."
$strFilter = "(&(objectCategory=Computer)(Name=$strComputer*))"
 
$objDomain = New-Object System.DirectoryServices.DirectoryEntry
 
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = $objDomain
$objSearcher.PageSize = 1000
$objSearcher.Filter = $strFilter
$objSearcher.SearchScope = "Subtree"
 
$colProplist = "name"
foreach ($i in $colPropList){[void]$objSearcher.PropertiesToLoad.Add($i)}
 
$colResults = $objSearcher.FindAll()
 
$colResults | %{ $a = New-Object PSObject ; $a | Add-Member -MemberType NoteProperty -Name Name -Value $_.Properties.Item("Name")[0] ; $a } | Export-Csv -NoTypeInformation "C:\MachineList.csv"

Open in new window

Avatar of Chris Dent
Chris Dent
Flag of United Kingdom of Great Britain and Northern Ireland image


It is possible to test for and capture an error condition. You want those where no error condition exists, which the snippet below does.

I've got it echoing the results immediately, and storing them in a $Results object so they can be reviewed on completion (and any other error messages can be checked, account is locked out, for example).

HTH

Chris
$LocalAdministrator = "Administrator"
$PasswordToTest = ""
$Computer = "PartialComputerName
 
$LdapFilter = "(&(objectCategory=Computer)(name=$Computer*))"
 
$Searcher = New-Object System.DirectoryServices.DirectorySearcher($Null, $LdapFilter)
$Searcher.PageSize = 1000
 
$PropertiesToLoad = @("name", "dNSHostName")
$Searcher.PropertiesToLoad.AddRange($PropertiesToLoad)
 
$Results = @()
$Searcher.FindAll() | %{
  # Variable that can be accessed within the Trap
  $Script:Exception = $Null
  # Capture any error
  Trap [Exception] {
    $Script:Exception = ($_.Exception.Message).Trim()
    Continue;
  }
 
  # Test binding to the Administrators group with the specified username and password
  [Void](New-Object System.DirectoryServices.DirectoryEntry(`
    "WinNT://$($_.Properties['dnshostname'])/Administrators, group", `
    "$($_.Properties['dnshostname'])\$LocalAdministrator", `
    $PasswordToTest, 0)).PsBase.Get_Name()
 
  # If no error is returned the the bind suceeded with this password
  If (!$Script:Exception) {
    Write-Host "$($_.Properties['dnshostname']): Successful bind using specified credentials"
    $Results += $_ | Select-Object @{n='HostName';e={ $_.Properties["dnshostname"]}}, `
      @{n='TestResult';e={ "Successful Bind" }}
  } Else {
    $Results += $_ | Select-Object @{n='HostName';e={ $_.Properties["dnshostname"]}}, `
      @{n='TestResult';e={ $Script:Exception }}
  }
}

Open in new window


Missed a " here:

$Computer = "PartialComputerName"

Sorry about that.

Chris
Avatar of goneal
goneal

ASKER

Thanks for all your work on this!
This looks to be pretty close...When I pass the first few letters of the machine names I am intersted in checking, in this case TAC, it writes a handful of them to the host with the expected "Successful bind using specified credentials".

I have approximately 80-100 machine names which begin with the digits TAC, and I know that there are more than just 4 or 5 with good credentials. When the script finishes executing, I can't echo to the host by entering $Results (it returns nothing) so I'm guessing the variable is empty. Would this mean that all machines pass the credentials test? If so, why were only a handful written to the host for viewing?

How should I modify the code so that each machine is reported with account status (whether binding with the credentials passed of failed) and then write the results to a CSV?

Hmm it should be writing all of them to the $Results array. I had it write both success and failure.

If you run this one it's own, does it return all of the machines we're interested in?

$Searcher.FindAll()

It's entirely possible the Trap is capturing too much, it would be worth running it without the Trap section to see if it errors for a large number of hosts.

Chris
Avatar of goneal

ASKER

When I run the code up to $Searcher.FindAll(), it returns 126 machines, when I run the entire routine, it returns 11 machines, all of which report success. The problem has got to be the trap, but I'm not sure how to rework it.

Lets run it without the Trap for now and see what it's returning to break it.

I'm sure it'll be that it's trapping too much. Ideally the trap should be more specific, I knew quick and easy would come back to bite ;)

Chris
$LocalAdministrator = "Administrator"
$PasswordToTest = ""
$Computer = "PartialComputerName
 
$LdapFilter = "(&(objectCategory=Computer)(name=$Computer*))"
 
$Searcher = New-Object System.DirectoryServices.DirectorySearcher($Null, $LdapFilter)
$Searcher.PageSize = 1000
 
$PropertiesToLoad = @("name", "dNSHostName")
$Searcher.PropertiesToLoad.AddRange($PropertiesToLoad)
 
$Results = @()
$Searcher.FindAll() | %{
  # Test binding to the Administrators group with the specified username and password
  (New-Object System.DirectoryServices.DirectoryEntry(`
    "WinNT://$($_.Properties['dnshostname'])/Administrators, group", `
    "$($_.Properties['dnshostname'])\$LocalAdministrator", `
    $PasswordToTest, 0)).PsBase.Get_Name()
}

Open in new window

Avatar of goneal

ASKER

The code errors on nearly everything now - the only successful value it appears to return is the text Administrators, which I assume to be a reference to the Administrators account group. Below is the record of errors:

*****************************************************************************************************************
Exception calling "get_Name" with "0" argument(s): "Logon failure: unknown username or bad password.

Exception calling "get_Name" with "0" argument(s): "The netowrk path was not found.

Exception calling "get_Name" with "0" argument(s): "Access id denied.

Exception calling "get_Name" with "0" argument(s): "There are currently no logon servers available to service the request.
*****************************************************************************************************************

No machine names were returned.

When I run the code to line 14: $Searcher.FindAll()

I notice that a few of the results return {name, adspath} instead of {dnshostname, name, adspath}

Incidentally, I've been asked to test the reverse of the original scenario - accounts with the proper password instead of a blank/null password, so I've modified line 2: $PasswordToTest = ""

so that it includes the actual expected admin password, which I don't think effects your code, it just reverses the logic (sort of).

Thanks again for your help!

If the DNS Name is always the same we can just use the machine name. And this one will echo the computer name.

Still no trapping, want that back in?

Chris
$LocalAdministrator = "Administrator"
$PasswordToTest = ""
$Computer = "PartialComputerName
 
$LdapFilter = "(&(objectCategory=Computer)(name=$Computer*))"
 
$Searcher = New-Object System.DirectoryServices.DirectorySearcher($Null, $LdapFilter)
$Searcher.PageSize = 1000
 
$PropertiesToLoad = @("name", "dNSHostName")
$Searcher.PropertiesToLoad.AddRange($PropertiesToLoad)
 
$Results = @()
$Searcher.FindAll() | %{
  # Echo the computer name
  Write-Host $_.Properties['name']
 
  # Test binding to the Administrators group with the specified username and password
  (New-Object System.DirectoryServices.DirectoryEntry(`
    "WinNT://$($_.Properties['name'])/Administrators, group", `
    "$($_.Properties['dnshostname'])\$LocalAdministrator", `
    $PasswordToTest, 0)).PsBase.Get_Name()
}

Open in new window

Avatar of goneal

ASKER

Still errors with same as above. Let's try the trap again.
ASKER CERTIFIED SOLUTION
Avatar of Chris Dent
Chris Dent
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of goneal

ASKER

That did the trick for me! The last piece I need is to be able to figure out how to position a pipeline for this so I can export the results to a CSV file for reporting.

Thanks again for all your help!

This should do, right at the end :)

$Results | Export-CSV "YourFile.csv"

Chris
Avatar of goneal

ASKER

Bingo! Again, thanks a million.