Tag Archives: AD Management

Correct data in mobile field in AD

A script changed the format of values in the Mobile field in AD. This however caused errors in the operation of soft tokens. I made an export of all mobile phone numbers and noticed that there were more errors. As so I’ve made a script that uses the substring method to correct the values. Please feel free to change it as desired.

$List2 = @()
$List = Get-ADUser -Filter * -Properties Mobile | ?{$_.Mobile -ne $Null}
Foreach ($Item in $List)
	{
	If ($Item.Mobile.Substring(0,2) -notmatch "06" -or $Item.Mobile.Length -ne "10")
		{
		$List2 += $Item
		}
	}

Foreach ($Item in $List2)
	{
	If ($Item.Mobile.Substring(0,3) -match "316"){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone ($item.mobile.Substring(0,3).Replace("316","06") + $Item.Mobile.Substring(3))}
	If ($Item.Mobile.Substring(0,1) -match "6"){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone ($item.mobile.Substring(0,1).Replace("6","06") + $Item.Mobile.Substring(1))}
	If ($Item.Mobile.Substring(0,4) -match "\+316"){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone ($item.mobile.Substring(0,4).Replace('+316',"06") + $Item.Mobile.Substring(4))}
	If ($Item.Mobile.Substring(0,5) -match "00316"){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone ($item.mobile.Substring(0,5).Replace("00316","06") + $Item.Mobile.Substring(5))}	
	If ($Item.Mobile.Substring(0,3) -match "013"){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone $Null}	
	If ($Item.Mobile -match " "){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone ($Item.Mobile.Replace(" ",""))}
	If ($Item.Mobile -match "-"){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone ($Item.Mobile.Replace("-",""))}
	If ($Item.Mobile -match "Geen"){Set-ADUser -Identity $Item.sAMAccountName -MobilePhone $Null}
	}

Compare AD User Properties

An administrator had to resolve an issue with a user account that could not sync with Share file. He thought that he had tried everything and so wanted to compare an Active Directory user that he copied from the trouble account with the actual user. The copied user did not have any trouble. After comparing he found that the user was member of an Active Directory Protected Group (aka Admincount=1). I made a Powershell script for him so he could compare the two.

Write-Host "Dit script vergelijkt twee Active Directory Users met elkaar."
$Outputdir = ".\exports\"
$Continue = "Y"
While ($Continue -eq "Y"){

While ($User1 -eq $Null){
$User1Value = Read-Host "Geef eerste user op"
$User1 = Get-ADUser $User1Value -Properties *}

While ($User2 -eq $Null){
$User2Value = Read-Host "Geef tweede user op"
$User2 = Get-ADUser $User2Value -Properties *
}

	$Usercomparison = @()
	$Usercomparison2 = @()

	$User1.GetEnumerator() | % {
		If ($User2.($_.Key) -eq $_.Value.value)
		{
			$Comparison = 'Gelijk'
		}
		else
		{
			$Comparison = 'Verschilt'
		}

		$UserObject = New-Object PSObject -Property ([ordered]@{
			Property = $_.Key
			$User1Value = $_.Value
			$User2Value = $User2.($_.Key)
			Overeenkomst = $Comparison
		})
		$UserObject2 = New-Object PSObject -Property ([ordered]@{
			Property = $_.Key
			$User1Value = $_.Value.Value
			$User2Value = $User2.($_.Key)
			Overeenkomst = $Comparison
		})
		$UserComparison += $UserObject
		$UserComparison2 += $UserObject2
	}

$UserComparison


while ($Difference -notmatch "[y|n]"){$Difference = read-host "Wil je filteren op gegevens die verschillen?(Y/N)"}

If ($Difference -eq "Y"){$UserComparison | ? {$_.Overeenkomst -match "Verschilt"} | FT}

while ($Choice -notmatch "[y|n]"){$choice = read-host "Wil je een export maken van de ongefilterde gegevens? (Y/N)"}
If ($Choice -eq "Y"){
Write-Host "Er wordt een export gemaakt van de bovenstaande gegevens op de locatie:"
If ((Test-Path ("$OutputDir"+"UserComparison-"+"$User1Value"+"$User2Value"+".csv")) -eq $True){
$List = GCI "$OutputDir" | ? {$_.FullName -match ("UserComparison-"+"$User1Value"+"$User2Value")}
[int]$Count = $list.count
$Count++}
If ($Count -ne $null){[string]$Count = ("-"+"$Count")}
$UserComparison2 | Export-csv -Path ("$OutputDir"+"UserComparison-"+"$User1Value"+"$User2Value"+"$Count"+".csv") -NoTypeInformation -Delimiter ";"
$Item = GI ("$OutputDir"+"UserComparison-"+"$User1Value"+"$User2Value"+"$Count"+".csv")
$Item.FullName
}
Remove-Variable User1,User1value,User2,User2value,Choice,Continue,Difference
While ($Continue -notmatch "[y|n]"){$Continue = Read-Host "Wil je nog een vergelijking uitvoeren? (Y/N)"}
}

Filtering and Arraying 101 Powershell

So I try to teach my colleagues a bit more about Powershell from time to time. I have advised them a bit about how they can filter values to meet their requirements. Our architect wanted an export of all user accounts. When I simply exported all AD users in the domain he said that the amount of users is way too large. He asked me how this happend and where all those users are coming from? I wanted to give him an export of all enabled users in ActiveDirectory per OU. Actually I just wanted to show him the amount of users per OU. So I made a little script that does that job. However this is an excellent opportunity for someone to start working with Powershell. So I’ve written a little 101 about how they should start.

Step 1

Import the module ActiveDirectory.

IPMO ActiveDirectory

Step 2

You want to know in what OU a user is situated. Maybe the CMDlet Get-ADUser returns this value. By running the command Get-ADUser SAMACCOUNTNAME -Properties * you’ll get all the available properties. To display the methods and functions you can also use this command, in correspondence with the CMDlet Get-Member (Alias GM). You’ll have to run:

Get-ADUser SAMACCOUNTNAME -Properties * | GM

This prints all available methods,functions,properties and so forth. If you see a property, that is name something like, parent or OU you’ll be lucky. Unfortunately in Powershell 1,2 and 3 there isn’t such a property. So you have to find other ways to get the parent OU.

Step 3

Many CMDlets can be used against the registry, file systems, certificate stores or the Active Directory. In this case you’ll still have to know the containing OU. So probably the best way is to use the CanonicalName which contains this information. However that would kill all the fun. In this example DistinguishedName will have to do it. This obviously displays the containing OU. To save the DistinguishedName in a string run:

$Result = (Get-ADUser SAMACCOUNTNAME).DistinguishedName

Step 4

The DistinguishedName contains the CN of a user. You want to filter that out of the string. But how do you do that? Again use GM to see if you can convert the value to it’s parent or the get what is possible with the value.  To do so run the command:

$Result | GM

You can replace the CN as text. This can be really really tricky. To do so run the command in step 3 without the .DistinguishedName property behind it. Next run the command:

$Result.DistinguishedName.Replace(("CN="+$Result.Name+","),'')

There is no right or wrong if the result is the same and it meets your goals. That is an important lesson to learn.

Step 5

To select an item use the CMDlet Get-Item (Alias GI). This CMDlet will let you select any file, folder or object in the registry,file system, certificate store or AD. Actually you can use it against any mounted PSdrive. We still want to know the containing OU. So in this case we will look up the DistinguishedName in the AD. Again use GM to find out what is possible with the output. To do so run:

GI "AD:\$Result" | GM

Step 6

As you can see the property PSParentPath exists. Maybe this displays the required output? Run the command:

(GI AD:\$Result).PSParentPath

Step 7

That output is not how you want it to be. The type is displayed. To be as clean as possible you will have to replace that part of the line. You can use the Replace method like this:

(GI AD:\$Result).PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')

Step 8

To come back to the objective, we’ll have to export all enabled users. There are two ways to accomplish this.

$List = Get-ADUser -Filter * | ? {$_.Enabled -eq $True}

Or

$List = Get-ADUser -Filter {Enabled -eq $True}

Which one is better? The correct answer would be none. The result is what counts. The best thing is running the command that you can remember or that works best for you. In terms of performance the second command would be better. Try this:

(Measure-Command -Expression {$list = Get-ADUser -Filter * | ?{$_.Enabled -eq $True}}).TotalSeconds

(Measure-Command -Expression {$list = Get-ADUser -Filter {Enabled -eq $true}}).TotalSeconds

Step 9

Next you can form a “script” to collect all DistinguishedNames and replace the unwanted text. To do so run:

Foreach ($item in $List){$Result = (GI AD:\$item).PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')
AC -Value $Result -Path ".\Output.csv"}

Step 10

We have a list called Output.csv. The only thing we have to do now is to count the number of occurrences. This is not necessary the best way to do it. You can save this output in an array if you like.  Below there are examples of many different ways to solve this problem. Below is an example of the last bit of a script.

$List2 = GC ".\Output.csv"
$Uniquelist = $list2 | Sort -Unique
Foreach ($UniqueItem in $Uniquelist){
[int]$Count = 0
Foreach ($Item in $list2){If ($Item -eq $UniqueItem){$Count++}}
$Outinfo = "$UniqueItem" + ";" + "$Count"
AC -Value $outinfo -Path ".\Counted OUs.csv"
}

There are lots of ways to get to your destination. I have generated a number of scripts to do this task.

Script 1

IPMO ActiveDirectory
$list = Get-ADUser -Filter * | ? {$_.Enabled -eq $True}
Foreach ($item in $List){$Result = (GI AD:\$item).psparentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')
AC -Value $Result -Path ".\Output.csv"}
$List2 = GC ".\Output.csv"
$Uniquelist = $list2 | Sort -Unique
Foreach ($UniqueItem in $Uniquelist){
[int]$Count = 0
Foreach ($Item in $list2){If ($Item -eq $UniqueItem){$Count++}}
$Outinfo = "$UniqueItem" + ";" + "$Count"
AC -Value $outinfo -Path ".\Counted OUs.csv"
}

This script generated two output files. A part of statics values is replaced.

Script 2

IPMO ActiveDirectory
$list = Get-ADUser -Filter * | ? {$_.Enabled -eq $True}
Foreach ($item in $list){$Result = $item.distinguishedName.Replace(("CN="+$item.Name+","),'')
AC -Value $Result -Path ".\Output.csv"}
$List2 = GC ".\Output.csv"
$Uniquelist = $list2 | Sort -Unique
Foreach ($UniqueItem in $Uniquelist){
[int]$Count = 0
Foreach ($Item in $list2){If ($Item -eq $UniqueItem){$Count++}}
$Outinfo = "$UniqueItem" + ";" + "$Count"
AC -Value $outinfo -Path ".\Counted OUs.csv"
}

This script contains a replace of dynamic content. You have to be very careful using such a replace. We have CN’s with a value like Berg, Steven van den. That comma is precisely the problem. To display a comma in the DistinguishedName a break is inserted. So the actual value is CN=Berg\, Steven van den,. Based on the specified condition this does not apply. Meaning that you will have inconsistent results. In script 3 this is corrected.

Script 3

IPMO ActiveDirectory
$list = Get-ADUser -Filter {Enabled -eq $True}
$List2 = @()
Foreach ($item in $list){$Result = $item.distinguishedName.Replace('\','').Replace(("CN="+$item.Name+","),'')
$List2 += $Result}
$Uniquelist = $list2 | Sort -Unique
Foreach ($UniqueItem in $Uniquelist){
[int]$Count = 0
Foreach ($Item in $list2){If ($Item -eq $UniqueItem){$Count++}}
$Outinfo = "$UniqueItem" + ";" + "$Count"
AC -Value $outinfo -Path ".\Counted OUs.csv"
}

Here the conditions are met. I have also included an empty array at the line 3. Next all values are added to that array. So only the correct file is exported. Be aware that is you do a double ” after the replace of the special character you’ll need a double backslash. In this case something like .Replace(“\\”,””). The backslash means a break stating a special character.

Even though this script delivers the correct output, I would still advise you to refrain from replacing dynamic content unless you are really sure that every condition is met and that you not filter out too much.

Script 4

IPMO ActiveDirectory
$list = (Get-ADUser -Filter {Enabled -eq $True}).DistinguishedName | %{(GI "AD:\$_").PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')}
$Uniquelist = $list | Sort -Unique
Foreach ($UniqueItem in $Uniquelist){
[int]$Count = 0
Foreach ($Item in $list2){If ($Item -eq $UniqueItem){$Count++}}
$Outinfo = "$UniqueItem" + ";" + "$Count"
AC -Value $outinfo -Path ".\Counted OUs.csv"
}

Script 1 uses the CMDlet Get-Item (Alias GI) to collect the parent OU. In script 1 this is done in separate commands. You can combine this in one line let the example above. I have done this by using the CMDlet Foreach-Object (Alias %).

Script 5

IPMO ActiveDirectory
$list = (Get-ADUser -Filter {Enabled -eq $True}).DistinguishedName | %{(GI "AD:\$_").PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')}
Foreach ($UniqueItem in ($List | Sort -Unique)){
[int]$Count = 0
Foreach ($Item in $list){If ($Item -eq $UniqueItem){$Count++}}
$Outinfo = "$UniqueItem" + ";" + "$Count"
AC -Value $outinfo -Path ".\Counted OUs.csv"
}

Unlike script 4 in shorted the script even further by not generation a separate unique item list.

Script 6

IPMO ActiveDirectory
$list = (Get-ADUser -Filter {Enabled -eq $True}).DistinguishedName | %{(GI "AD:\$_").PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')}
Foreach ($UniqueItem in ($List | Sort -Unique)){
[int]$Count = 0
Foreach ($Item in $list){If ($Item -eq $UniqueItem){$Count++}}
AC -Value ("$UniqueItem" + ";" + "$Count") -Path ".\Counted OUs.csv"
}

The output string is combined in one line.

Script 7

IPMO ActiveDirectory
Get-ADUser -Filter {Enabled -eq $True} | % {($_).distinguishedName.Replace('\','').Replace(("CN="+$_.Name+","),'')} | Group | Select Name,Count | epcsv ".\Counted OUs.csv" -Delimiter ";" -NoTypeInformation

This example is even prettier. Nearly a oneliner. To count we use the Group-Object (Alias Group) te count the number of occurrences. Next we Select the fields we want. Followed by exporting the data by Export-CSV (Alias epcsv). This can be done with that other command aswel.

Script 8

IPMO ActiveDirectory
(Get-ADUser -Filter {Enabled -eq $True}).DistinguishedName | %{(GI "AD:\$_").PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')} | Group | Select Name,Count | epcsv ".\Counted OUs.csv" -Delimiter ";" -NoTypeInformation

In this case you have few ways to select the data. Like these:

IPMO ActiveDirectory
Get-ADUser -Filter {Enabled -eq $True} | %{(GI AD:\$_).PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')} | Group | Select Name,Count | epcsv ".\Counted OUs.csv" -Delimiter ";" -NoTypeInformation

Or

IPMO ActiveDirectory
Get-ADUser -Filter {Enabled -eq $True} | %{(GI AD:\$_).PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')} | Group |  % { AC -Value ("$($_.Name)" + ";" + "$($_.Count)") -Path ".\Counted OUs.csv"}

Or

IPMO ActiveDirectory
Get-ADUser -Filter {Enabled -eq $True} | %{(GI AD:\$_).PSParentPath.Replace("Microsoft.ActiveDirectory.Management\ActiveDirectory:://RootDSE/",'')} | Group  | % { AC -Value ($($_.Name) + ";" +  ($($_).Group.Count)) -Path ".\Counted OUs.csv"}

Except one, these scripts all generate the same output. Know that there are many more ways to get the same output. Again the best method is the one you can remember and you feel comfortable using. If you like oneliners you’ll prefer script 8. If you like to have an overview of what is happening you’ll prefer script 1. Based on your skills in scripting/Powershell you’ll choose anything in between.

You could also have selected CanonicalName. This would have been a lot easier as it doesn’t contain any breaking (\). Another advantage would be that you can sort alphabetically. Since DistinguishedName is formed from the deepest to the highest level is it only possible to sort based on the last OU. Normally you would want to have this export correspond with the hierarchy of Active Directory. This is only possible with CanonicalName. CanonicalName is built up from highest in the hierarchy to the lowest container.

Reference table

Alias CMDlet
IPMO  Import-Module
 GM  Get-Member
 GI  Get-Item
 ?  Where-Object
AC  Add-Content
 GC  Get-Content
 Sort Sort-Object
 % Foreach-Object
 Group  Group-Object
 Select  Select-Object
epcsv  Export-CSV

I hope you’ve had fun!

Export all tasks in the domain

In the spirit of controlling all tasks you can do an export of them all in your domain. To do this you can use to following script. This script uses the schtasks.exe. It exports the scheduled tasks on any server in the domain. The list of those servers is generated based on when the have logged on to the domain. I explained that in this post.

You can adjust this code to your needs.

Ipmo ActiveDirectory
$Date = (Get-Date).AddDays(-15)
$list = Get-ADComputer -Filter 'OperatingSystem -like "Windows Server 20*" -and LastLogonDate -gt $Date' -Properties LastLogonDate,Operatingsystem
Foreach ($Computer in $list){$Export = $Null
$Export = schtasks /query /FO CSV /V /S $Computer.DNSHostName
$Path = ".\"+$Computer.Name+".csv"
If ($Export -ne $Null){Foreach ($Line in $Export){AC -Value $Line -Path $Path}}
If ($Export -eq $Null){AC -Value $Computer.DNSHostName -Path ".\Error.csv"}}
$CSV = gci .\* -Include *.csv | ? {$_.FullName -notmatch "Error.csv"}
$List = @()
$List += Import-CSV $CSV -Delimiter ","
$List | Export-CSV ".\CompleteExport.csv" -Delimiter ";" -Append -NoTypeInformation

 

Administrator account monitor script

The Administrator account is an account which you must monitor in a managed environment. You do not want this account to be exploited. For example I recently encountered an Administrator account which had a mailbox. To make matters worse the account had ActiveSync enabled. So if someone would authenticate with the Administrator account to Activesync it would become visible when you have the password right. A blackhat hacker could do a long term deployment in which he would try to authenticate multiple times per hour. He would be unnoticed in his attempts as long as he keeps the pace low. Imagine what a massive number of passwords you can try in two years time. If you have chosen a bad Administrator password you could end up being compromised. 105120 may seem like a low number but in reality many companies do not change this password frequently and if those companies started out with a weak password, the chance of being compromised is quite large.

What most companies also don’t do is monitor when someone logged on with this account recently.  Or more importantly when someone reset the password. This information is very important to keep an eye on. I made a script which can be planned to report any of those events by mail. You may adjust as you wish.

I highly recommend to sign this script to prevent rogue administrator scenarios. If you want to monitor any powerful group in your domain I have made a script that does that job similar to to one below

<#Administrator Test Script#>
$MailServer = "mailserver"
$Recipients = "some@one.com","any@body.com"
cd "Path-to-Script..."

$Domaincontrollers = [system.directoryservices.activedirectory.domain]::GetCurrentDomain() | ForEach-Object {$_.DomainControllers} | ForEach-Object {$_.Name} 
$Errors =@()
Foreach ($Domaincontroller in $DomainControllers){$Path = ".\"+$Domaincontroller+".csv"
If ((Test-Path $Path) -eq $False){
Get-ADUser Administrator -Properties * -Server $DomainController | Select Enabled,LastBadPasswordAttempt,LastLogon,LastLogonDate,LastLogonTimeStamp,LockedOut,PasswordLastSet | Export-CSV -Delimiter ";" -Path $Path
}}
$CSV = GCI ".\*" -Include *.csv
Foreach ($Item in $CSV){
$StoredValues = Import-CSV $Item.FullName -Delimiter ";"
$DC = $Item.Name.Replace(".csv",'')
Get-ADUser Administrator -Properties * -Server $DC | Select Enabled,LastBadPasswordAttempt,LastLogon,LastLogonDate,LastLogonTimeStamp,LockedOut,PasswordLastSet | Export-CSV -Delimiter ";" -Path ".\Temp.csv"
$ActualValues = Import-CSV ".\Temp.csv" -Delimiter ";"

$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property Enabled)
If ($CompareResult -ne $Null){$Errors += "Enabled"+";"+"$DC"+";"+$ActualValues.Enabled+";"+$StoredValues.Enabled}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LastBadPasswordAttempt)
If ($CompareResult -ne $Null){$Errors += "LastBadPasswordAttempt"+";"+"$DC"+";"+$ActualValues.LastBadPasswordAttempt+";"+$StoredValues.LastBadPasswordAttempt}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LastLogon)
If ($CompareResult -ne $Null){$Errors += "LastLogon"+";"+"$DC"+";"+$ActualValues.LastLogon+";"+$StoredValues.LastLogon}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LastLogonTimeStamp)
If ($CompareResult -ne $Null){$Errors += "LastLogonTimeStamp"+";"+"$DC"+";"+$ActualValues.LastLogonTimeStamp+";"+$StoredValues.LastLogonTimeStamp}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LockedOut)
If ($CompareResult -ne $Null){$Errors += "LockedOut"+";"+"$DC"+";"+$ActualValues.LockedOut+";"+$StoredValues.LockedOut}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property PasswordLastSet)
If ($CompareResult -ne $Null){$Errors += "PasswordLastSet"+";"+"$DC"+";"+$ActualValues.PasswordLastSet+";"+$StoredValues.PasswordLastSet}
Remove-Item ".\Temp.csv" -Force
}

If ($Errors.length -ne 0){$Body = "Er heeft een wijziging plaats gevonden op het account Administrator. De Fout/Fouten zijn:"+"`n`n"
Foreach ($item in $Errors){
$Array = $Item.Split(";")
If ($Array[0] -Match "Enabled"){$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de status van het account gewijzigd van Enabled: "+$Array[3]+" naar Enabled: "+$Array[2]+".`n"}
If ($Array[0] -Match "LastBadPasswordAttempt"){$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is er een BadPasswordAttempt uitgevoerd op "+$Array[2]+". De waarde was hiervoor: "+$Array[3]+".`n"}
If ($Array[0] -Match "LastLogon"){$StoredValue = $Array[3] ; $StoredValue = [datetime]::fromfiletime($StoredValue) ; $ActualValue = $Array[2] ; $ActualValue =  [datetime]::fromfiletime($ActualValue) ; $Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de waarde van LastLogon gewijzigd van "+$StoredValue+" naar de waarde: "+$ActualValue+".`n"}
If ($Array[0] -Match "LastLogonTimeStamp"){$StoredValue = $Array[3] ; $StoredValue = [datetime]::fromfiletime($StoredValue) ; $ActualValue = $Array[2] ; $ActualValue =  [datetime]::fromfiletime($ActualValue) ; $Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de waarde van LastLogonTimeStamp gewijzigd van "+$StoredValue+" naar de waarde: "+$ActualValue+".`n"}
If ($Array[0] -Match "LockedOut"){$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de status van het account gewijzigd van LockedOut: "+$Array[3]+" naar LockedOut: "+$Array[2]+".`n"}
If ($Array[0] -Match "PasswordLastSet"){$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is er een PasswordLastSet uitgevoerd op "+$Array[2]+". De waarde was hiervoor: "+$Array[3]+".`n"}
}
Send-MailMessage -From "AdministratorRoles@Domain.com" -Subject "Er heeft een wijziging plaatsgevonden op het Administrator account" -To $Recipients  -Body $Body -SmtpServer $MailServer}

Foreach ($item in $CSV){Remove-item $Item -Force}
Foreach ($Domaincontroller in $DomainControllers){$Path = ".\"+$Domaincontroller+".csv"
Get-ADUser Administrator -Properties * -Server $DomainController | Select Enabled,LastBadPasswordAttempt,LastLogon,LastLogonDate,LastLogonTimeStamp,LockedOut,PasswordLastSet | Export-CSV -Delimiter ";" -Path $Path}




EDIT 30 April 2015

Since the number of e-mails sent is very high, I was asked to change the script to show where the error originated from. I found that in the Security event log on the specified domain controller an error is written with that information. The problem is that it will not export very well. If you select the Message string of that Event log item you can not simply select the originating address.

Screen Shot 2015-04-30 at 10.53.50

The EventID number is 4771. You can see the additional information in the image above. So I changed the script so that it will search the event log of the domain controller on which the authentication failure occurred. You’ll have to make sure that the event log is not too big. I have used this script with an event log that was 1 GB of size. I have had serious problems with the script because it cannot index anywhere from 1.8 million records in the memory. You can get by this to select -Newest 200000 in the Get-EventLog cmdlet. However you’ll have less accuracy as the event log item can be outside that limit in some cases. I do not know this for certain but I have a hunch that Get-EventLog will search from bottom to top. We have used the script successfully with an event log size of 128 MB. Even an event log of that size does pose a problem as querying that does take a lot of time. The best thing to do is the run the script like every 2 minutes and specify the first 50K records.

Anyhow I noticed that there is some difference between that event log per domain controllers. This is just like the case further above that when you query for when the last badpassword attempt has occurred there’s a difference per domain controller. So just like that I made sure that the domain controller on which the bad password attempt occurred is being queried. You need to have Powershell version 3 . To check which version of Powershell is installed enter this code.

<#Administrator Test Script#>
$MailServer = "mailserver"
$Recipients = "some@one.com","any@body.com"
cd "Path-to-Script..."

$Domaincontrollers = [system.directoryservices.activedirectory.domain]::GetCurrentDomain() | ForEach-Object {$_.DomainControllers} | ForEach-Object {$_.Name} 
$Errors =@()
Foreach ($Domaincontroller in $DomainControllers){$Path = ".\"+$Domaincontroller+".csv"
If ((Test-Path $Path) -eq $False){
Get-ADUser Administrator -Properties * -Server $DomainController | Select Enabled,LastBadPasswordAttempt,LastLogon,LastLogonDate,LastLogonTimeStamp,LockedOut,PasswordLastSet | Export-CSV -Delimiter ";" -Path $Path
}}
$CSV = GCI ".\*" -Include *.csv
Foreach ($Item in $CSV){
$StoredValues = Import-CSV $Item.FullName -Delimiter ";"
$DC = $Item.Name.Replace(".csv",'')
Get-ADUser Administrator -Properties * -Server $DC | Select Enabled,LastBadPasswordAttempt,LastLogon,LastLogonDate,LastLogonTimeStamp,LockedOut,PasswordLastSet | Export-CSV -Delimiter ";" -Path ".\Temp.csv"
$ActualValues = Import-CSV ".\Temp.csv" -Delimiter ";"

$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property Enabled)
If ($CompareResult -ne $Null){$Errors += "Enabled"+";"+"$DC"+";"+$ActualValues.Enabled+";"+$StoredValues.Enabled}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LastBadPasswordAttempt)
If ($CompareResult -ne $Null){$Errors += "LastBadPasswordAttempt"+";"+"$DC"+";"+$ActualValues.LastBadPasswordAttempt+";"+$StoredValues.LastBadPasswordAttempt}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LastLogon)
If ($CompareResult -ne $Null){$Errors += "LastLogon"+";"+"$DC"+";"+$ActualValues.LastLogon+";"+$StoredValues.LastLogon}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LastLogonTimeStamp)
If ($CompareResult -ne $Null){$Errors += "LastLogonTimeStamp"+";"+"$DC"+";"+$ActualValues.LastLogonTimeStamp+";"+$StoredValues.LastLogonTimeStamp}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property LockedOut)
If ($CompareResult -ne $Null){$Errors += "LockedOut"+";"+"$DC"+";"+$ActualValues.LockedOut+";"+$StoredValues.LockedOut}
$CompareResult =@(Compare -ReferenceObject $StoredValues -DifferenceObject $ActualValues -Property PasswordLastSet)
If ($CompareResult -ne $Null){$Errors += "PasswordLastSet"+";"+"$DC"+";"+$ActualValues.PasswordLastSet+";"+$StoredValues.PasswordLastSet}
Remove-Item ".\Temp.csv" -Force
}

If ($Errors.length -ne 0){$Body = "Er heeft een wijziging plaats gevonden op het account Administrator. De Fout/Fouten zijn:"+"`n`n"
Foreach ($item in $Errors){
$Array = $Item.Split(";")
If ($Array[0] -Match "Enabled"){$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de status van het account gewijzigd van Enabled: "+$Array[3]+" naar Enabled: "+$Array[2]+".`n"}
If ($Array[0] -Match "LastBadPasswordAttempt"){
$Resultlist = @()
$Eventlog=@( Get-EventLog -LogName Security -ComputerName $Array[1] | ?{$_.entrytype -eq "FailureAudit" -and $_.message -match "Administrator"})
Foreach ($Event in $Eventlog){$Message = $Event.message.Split("`n")
Foreach ($Line in $Message){
If ($Line -match "Client Address:"){
[String]$Result = $Line.replace("`t",'').Replace("Client Address:",'').Replace(" ",'').Replace("::ffff:",'')}}
$ReverseName = $Null
[string]$ReverseName = [System.Net.Dns]::GetHostbyAddress($Result.Trim()).hostname
If ($ReverseName -eq $Null){$ReverseName = "Naam kon niet opgehaald worden"}
$Value = "- IP-Adres: "+($Result.replace("`n",'')) + " welke resolved naar $ReverseName`n"
$Resultlist += $Value}
$Resultlist = $Resultlist | Sort -Unique
$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is er een BadPasswordAttempt uitgevoerd op "+$Array[2]+". De waarde was hiervoor: "+$Array[3]+". De attempts kwamen vanaf de volgende computer/Computers:`n`r"
$Body = $Body + $Resultlist + "`n`n"}
If ($Array[0] -Match "LastLogon"){$StoredValue = $Array[3] ; $StoredValue = [datetime]::fromfiletime($StoredValue) ; $ActualValue = $Array[2] ; $ActualValue =  [datetime]::fromfiletime($ActualValue) ; $Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de waarde van LastLogon gewijzigd van "+$StoredValue+" naar de waarde: "+$ActualValue+".`n"}
If ($Array[0] -Match "LastLogonTimeStamp"){$StoredValue = $Array[3] ; $StoredValue = [datetime]::fromfiletime($StoredValue) ; $ActualValue = $Array[2] ; $ActualValue =  [datetime]::fromfiletime($ActualValue) ; $Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de waarde van LastLogonTimeStamp gewijzigd van "+$StoredValue+" naar de waarde: "+$ActualValue+".`n"}
If ($Array[0] -Match "LockedOut"){$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is de status van het account gewijzigd van LockedOut: "+$Array[3]+" naar LockedOut: "+$Array[2]+".`n"}
If ($Array[0] -Match "PasswordLastSet"){$Body = $Body + "Op de domaincontroller: "+$Array[1]+" is er een PasswordLastSet uitgevoerd op "+$Array[2]+". De waarde was hiervoor: "+$Array[3]+".`n"}
}
Send-MailMessage -From "AdministratorRoles@Domain.com" -Subject "Er heeft een wijziging plaatsgevonden op het Administrator account" -To $Recipients  -Body $Body -SmtpServer $MailServer}

Foreach ($item in $CSV){Remove-item $Item -Force}
Foreach ($Domaincontroller in $DomainControllers){$Path = ".\"+$Domaincontroller+".csv"
Get-ADUser Administrator -Properties * -Server $DomainController | Select Enabled,LastBadPasswordAttempt,LastLogon,LastLogonDate,LastLogonTimeStamp,LockedOut,PasswordLastSet | Export-CSV -Delimiter ";" -Path $Path}


Export PasswordNeverExpires for all accounts in the domain

A simple script to export all accounts in the domain that have the PasswordNeverExpires property set on True

ipmo ActiveDirectory 
$list = Get-ADUser -Filter 'PasswordNeverExpires -eq $True' -Properties PasswordNeverExpires | Select Name,SamAccountName,PasswordNeverExpires
$List | Export-CSV ".\PasswordNeverExpires.csv" -Delimiter ";" -Append -NoTypeInformation

You can make it more complex by adding filters based on the OU.If Users that you want to monitor are in the OU’s Accounts,Employees and Admins you can enter the code below. Adjust it to your needs.

ipmo ActiveDirectory 
$list = Get-ADUser -Filter 'PasswordNeverExpires -eq $True' -Properties PasswordNeverExpires | ? {$_.DistinguishedName -match ",OU=Accounts|,OU=Employees|,OU=Admins" | Select Name,SamAccountName,PasswordNeverExpires
$List | Export-CSV ".\PasswordNeverExpires.csv" -Delimiter ";" -Append -NoTypeInformation

Export services

Recently I needed to have an export of all services. To be more specific I needed to have an export of Log On As field of all services. I wanted to have this export for alle computers in de domain. I wanted only servers but you can modify the script as you wish. For this to work you’ll need to have PSRemoting enabled on all servers. I wrote a script that does that job.

I added a LastLogonDate as that can confirm that the specified server is logged on lately. I used 15 days because the LastLogonTimeStamp isn’t a hard value. The Timestamp can fall behind anywhere from 14 to 8 days. So by specifying 15 days you are safe.

Ipmo ActiveDirectory
$Date = (Get-Date).AddDays(-15)
$list = Get-ADComputer -Filter 'OperatingSystem -like "Windows Server 20*" -and LastLogonDate -gt $Date' -Properties LastLogonDate,Operatingsystem

Foreach ($Computer in $List){$Export = $Null
$Export = Get-WmiObject win32_service -ComputerName $Computer.DnsHostName | select Name,Startname,Systemname
If ($Export -ne $Null){$Export | Export-CSV ".\Services.csv" -Delimiter ";" -Append -NoTypeInformation}
If ($Export -eq $Null){AC -Value $Computer.DNSHostName -Path ".\Error.csv"}}

Monitor members of the Administrative Group

In a domain with multiple Administrators a rogue Administrator case should be avoided. Some Administrators want to built a backdoor into the domain for whatever reason. Managing these backdoors should be a priority. I have written a script that queries members of the Administrative groups that you might want to monitor. The script than compares it with a reference list that was previously created. If an Administrator is added or removed from one of the group you’ll be notified by mail. To make this work you need to schedule this Powershell script. You’ll also need to have relay permission to a SMTP server from where you run the script. In my case I added a schedule of once an hour. The script expects a file called “Groepenlijst” in which you add the groups.

Import-Module ActiveDirectory
$Path = "LocationOfFile"
$Groups = GCI $Path | Where {$_.FullName -match "Groepenlijst"} | Get-Content
$MailServer = "Mailserver"
$Recipients = "Recipient@Domain.TLD","Recipient@Domain.TLD","Recipient@Domain.TLD"

Foreach ($Group in $Groups){
$Result = GCI $Path | Where {$_.FullName -match $Group}
If ($Result -eq $null){$Members = Get-ADGroupMember $Group -recursive | Select SamAccountName
$File = ($Path+"\"+$Group+".csv")
$Members | Export-CSV -Path $File -Delimiter ";" -NoTypeInformation
}}

Foreach ($Group in $Groups){
$Result = GCI $Path | Where {$_.FullName -match $Group}
$DifferenceList = Import-CSV $Result.FullName
$ReferenceList =  Get-ADGroupMember $Group -recursive | Select SamAccountName
$MembersRemoved = @()
$MembersAdded = @()
$CompareResult =@( Compare -Property SamAccountName -ReferenceObject $ReferenceList -DifferenceObject $DifferenceList)

Foreach ($item in $CompareResult){
If ($item.sideindicator -eq "<="){$MembersAdded += $Item}
If ($item.sideindicator -eq "=>"){$MembersRemoved += $Item}
}

If ($MembersAdded.count -ne 0){
If ($Body -ne $null){
If ($MembersAdded.Count -gt 1){$Body = $Body +"`n`n" + "Er zijn nieuwe members toegevoegd aan de groep: "+ $Group + ". De members zijn:" + "`n`n"
Foreach ($Item in $MembersAdded){$Body = $Body + $item.samaccountName + "`n"}}
If ($MembersAdded.Count -eq 1){$Body = $Body + "`n`n" + "Er is een nieuwe member toegevoegd aan de groep: "+ $Group + ". De member is:" + "`n`n" + $MembersAdded.samaccountName}}
If ($Body -eq $null){
If ($MembersAdded.Count -gt 1){$Body = "Er zijn nieuwe members toegevoegd aan de groep: "+ $Group + ". De members zijn:" + "`n`n"
Foreach ($Item in $MembersAdded){$Body = $Body + $item.samaccountname + "`n"}}
If ($MembersAdded.Count -eq 1){$Body = "Er is een nieuwe member toegevoegd aan groep: "+ $Group + ". De member is:" + "`n`n" + $MembersAdded.samaccountname}}
}
If ($MembersRemoved.count -ne 0){
If ($Body -ne $null){
If ($MembersRemoved.Count -gt 1){$Body = $Body +"`n`n" + "Er zijn members verwijderd uit de groep: "+ $Group + ". De members zijn:" + "`n`n"
Foreach ($Item in $MembersRemoved){$Body = $Body + $item.samaccountName + "`n"}}
If ($MembersRemoved.Count -eq 1){$Body = $Body + "`n`n" + "Er is een member verwijderd uit de groep: "+ $Group + ". De member is:" + "`n`n" + $MembersRemoved.samaccountName}}
If ($Body -eq $null){
If ($MembersRemoved.Count -gt 1){$Body = "Er zijn members verwijderd uit de groep: "+ $Group + ". De members zijn:" + "`n`n"
Foreach ($Item in $MembersRemoved){if ($item.sideindicator -eq "=>"){$Body = $Body + $item.samaccountname + "`n"}}}
If ($MembersRemoved.Count -eq 1){$Body = "Er is een member verwijderd uit de groep: "+ $Group + ". De member is:" + "`n`n" + $MembersRemoved.samaccountname}}
}}

If ($Body -ne $null){
Send-MailMessage -From "FromAddress@Domain.TLD" -Subject "Nieuwe of verwijderde Member(s) voor administratieve groepen" -To $Recipients  -Body $Body -SmtpServer $MailServer
}

Foreach ($Group in $Groups){
$Members = Get-ADGroupMember $Group -recursive | Select SamAccountName
$File = ($Path+"\"+$Group+".csv")
$Members | Export-CSV -Path $File -Delimiter ";" -NoTypeInformation -Force
}

In my “groepenlijst” I have added the following groups:

Domain Admins
Enterprise Admins
Schema Admins
Administrators
Backup Operators
Organization Management
Server Operators
Server Management

Adjust this at will.

 

Find members of a local group

We needed a list of all local admins in the domain. I found some code somewhere and adjusted it to my personal needs.

Import-Module ActiveDirectory

$date = (get-date).AddDays(-35)
$list = (Get-ADComputer -Filter {(OperatingSystem -like "*Server*") -and (PasswordLastSet -ge $Date)} ).Name

$Result = @()

foreach($server in $list){

$computer = [ADSI](”WinNT://” + $server + “,computer”)
$Group = $computer.psbase.children.find(”Administrators”)

function getAdmins

{$members = $Group.psbase.invoke(”Members”) | %{$_.GetType().InvokeMember(”Adspath”, ‘GetProperty’, $null, $_, $null)}
$members}

$Result += $server
$Result += ( getAdmins )
$Result += " "
}
$Result | ac c:\LocalAdmins.csv

Using filters to find groups with mail address

You can export a list of all groups with the field mail enabled by running the following code:

$list = Get-ADGroup -Filter * -Properties * | Where {$_.mail -ne $null -and $_.GroupScope -match "Global"}
$list | export-csv -Path Globalmetmail.csv -Delimiter ";"

To find groups inside a certain OU for example if you have a distribution OU you may want to export certain Grouptypes:

$list2 = Get-ADGroup -Filter * -Properties * | Where {$_.CanonicalName -match "Distribution" -and $_.GroupScope -match "Global"}
$list2 |export-csv -Path GlobalEnInDistributionOU.csv -Delimiter ";"