All posts by steven

Find user session on all active servers

If you want to find a session on all active servers in the domain you can make use of the command quser /server:whatever. This displays the session details from that server. This is handy if some administrator has locked his account because of old sessions on some server. There’re alternative ways to find this. I made a post awhile back. See it here.

IPMO ActiveDirectory

$Username = "SAMACCOUNTNAME"
$List2 = @()

$List = Get-ADComputer -Properties passwordlastset,operatingsystem -Filter * | ?{$_.PasswordLastSet -gt (Get-Date).Adddays(-30) -and $_.OperatingSystem -match "Server"}

Foreach ($item in $list){
	[String]$Server = $item.Name
	$Result =@( (quser /server:$Server) -replace '\s{2,}',','|ConvertFrom-Csv)
		Foreach ($object in $result){
			If ($Object.UserName -Match $Username){$Output = ($Server + ";" + $Object.UserName + ";" + $Object.State)
			$List2 += $Output
		}
	}
} 

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)"}
}

vCenter VM reboot script

Once in a while I run into problems that are really fun resolving. A colleague  of mine said that he was early today to fix the XenApp servers that did not reboot successfully. He said that since the storage of the Citrix Provisioning server was moved from flash to SAS the XenApp servers sometimes cannot boot. A shortage in IOPS is supposed to be the cause of this trouble. However all Provisioning servers combined consume over a TeraByte of storage. During the day this fast storage does not is nearly idle. Only during boot of the XenApp machines it is used in an appropriate manner. So that is quite costly.

VMWare created CMDlet’s the right way. You can basically do anything you can in GUI via Powershell using the PowerCLI.  The PowerCLI is an incredible tool in automatising stuff. We have RES Automation Manager. So I put the bits together and created A Powershell script in RES Automation Manager that runs every night. This script uses a few parameters which you can see in the code in the top section of the script. The script heavily relies on PowerCLI 6.0 This version works for any vCenter server.

The script connects to a VIserver (in this case the vCenter server). Next it gathers a list of all servers. Then it filters server based on the condition if it is PoweredOn and it matches on Name. Next it gets the VMware Tools status. If the status is NotRunning it will be applicable for a restart. The servers will be rebooted in a staggered way. So every two minutes a server will reboot. There’ll be a second attempt if the first attempt fails. At last it sends an e-mail with all server that fit the condition the first time, the second time and server that did not rebooted well.

I hope you can use it.

# Adds the base cmdlets
Add-PSSnapin VMware.VimAutomation.Core
$Recipients = $[Recipients]
$VIServer = "$[VIServer]"
$NotCondition = "$[NotCondition]"
$Condition = "$[Condition]"
$Username = [Environment]::UserName
$Hostname = $env:COMPUTERNAME
$Mailserver = "$[MailServer]"
$Level = "0"
Connect-VIServer $VIServer

#List bouwen van VM's die voldoen aan de voorwaarden
$List =@(Get-VM | ? {$_.Name -match $Condition -and $_.PowerState -match "PoweredOn" -and $_.Name -notmatch $NotCondition} | Get-VMGuest | ? {$_.State -match "NotRunning"})

#Check of de PowerCLi wel geinstalleerd is
If (((Get-PSSnapin).name -match "VMware.VimAutomation") -eq $Null){$Body = "De computer $Hostname heeft geen PowerCLI geinstalleerd staan. Installeer PowerCLI en probeer het opnieuw "; $Level = "1"}

#Check of er een verbinding is met de VI server
If ($global:DefaultVIServer -eq $null -and $Level -ne "1"){$Body = "Kon geen verbinding krijgen met de VCenter Server: $VIServer. Verander de waarde in het script als de servernaam veranderd is. Controleer of de user $UserName rechten heeft op de VCenter server. Controleer eventueel of de PowerCLI tools op de server wel compatible zijn met de Vcenter Server versie." ; $Level = "2"}

#Geen servers gevonden
If ($list.count -eq "0" -and $Level -eq "0"){$Body = "Er zijn geen server(s) gevonden welke voldoen aan de voorwaarden."}

#Actie bij wel servers gevonden
If ($list.count -gt "0" -and $Level -eq "0"){
$Body = "Er waren "+ $list.count +" server(s) die voldeden aan de voorwaarden. Het gaat om de server(s):`n`n"
$Body = "Er worden "+ $list.count +"servers herstart. De servers zijn:`n`n"
Foreach ($item in $list){$Body = $Body + $Item.vm + "`r"}

#Herstarten van servers
Foreach ($item in $list){Restart-VM -VM $Item.VM -Confirm:$False ; Start-Sleep -s 120}

#Check of er nu geen servers meer zijn die voldoen aan de voorwaarden
$List2 =@(Get-VM | ? {$_.Name -match $Condition -and $_.PowerState -match "PoweredOn" -and $_.Name -notmatch $NotCondition} | Get-VMGuest | ? {$_.State -match "NotRunning"})

#Actie bij wel servers gevonden die nog niet goed herstart zijn
If ($List2.Count -ne "0"){
$Body = $Body + "`n`n Er waren server die nog steeds niet goed herstart waren. De naam van deze server(s) waren:`n`n"
Foreach ($item2 in $list2){$Body = $Body + $Item2.vm + "`r"}
$Body = $Body + "`n`n Er worden geprobeerd de server opnieuw te herstarten."
Foreach ($item2 in $list2){Restart-VM -VM $Item2.VM -Confirm:$False ; Start-Sleep -s 120}
#Laatste check
$List3 =@(Get-VM | ? {$_.Name -match $Condition -and $_.PowerState -match "PoweredOn" -and $_.Name -notmatch $NotCondition} | Get-VMGuest | ? {$_.State -match "NotRunning"})
If ($List3.Count -ne "0"){
$Body = "Er zijn fouten gevonden bij het herstarten van servers. Zie onderaan de e-mail!!" + $Body
$Body = $Body + "`n`n De server die niet herstart zijn na twee pogingen zijn:`n`n"
Foreach ($item3 in $list3){$Body = $Body + $Item3.vm + "`r"}
}}}

#Mail versturen
Send-MailMessage -From "Rebootscript@domain.tld" -Subject "VMs rebooten" -To $Recipients -Body $Body -SmtpServer $MailServer

#Output naar Console in het geval dat er geen mail verstuurd is.
Write-host `n`n Mail Body`n
$Body

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!

Advanced selecting

When you want to filter data in Powershell you can do this on various ways. I wanted to move folders on which the security group did not correspond with the naming convention. The code below does the job.

$list = gci "\\server\share"
Foreach ($item in $list){
If(((((get-acl $item.fullname).access).identityreference).value -match "GroupSyntax").count -eq 0){
Move-Item $item.FullName -Destination "\\destination\Share"
}}

Repair autocomplete in Outlook with RES WM and Zero-profiling

Sometimes things that are meant to make your life easier make it harder. As is the case with RES Workspace Manager with Zero-profiling. If you have published Outlook 2010 or 2013 in RES Workspace Manager and you’ve enabled Zero-profiling fixing a corrupted Autocomplete dat file is not an easy job. Luckily there is a tool called Nk2edit. With NK2edit you can repair and edit NK2 and Autocomplete.dat files. The nice thing about NK2edit is that it has command line parameters. So in my case I made a little Powershell script that does the job.

When a user opens Outlook 2010 a couple of files are being loaded out of the Zero profile. They are loaded under C:\Users\$Username\Appdata\Local\Microsoft\Outlook. A file called Stream_Autocomplete_XXXXX.dat is created. In that file the is the autocomplete data stored. All you helpdesk have to do is enter the UNC path of that file in the script. Request the user to close Outlook and let the script do it’s thing. In the background you regenerate the autocomplete file by simple load and save it. This resolves the problem. To do so I let the /Script parameter refer to an empty script file. This manipulates nk2edit to load and save the .dat file. For the script to work you’ll need to download NK2edit.exe. You do not need a commercial license for the script to work, however if you use it commercially you of course do need one ;-).

$Choice = "y"
While ($Choice -eq "Y"){
[console]::ForegroundColor = "Cyan"
Write-Host "Vul de locatie van het autocomplete bestand:" -NoNewline
$Locatie = Read-host

$Result = Test-Path $Locatie
While ($Result -ne $True){Write-Host "De opgegeven locatie bestaat niet. Vul opnieuw in:" -NoNewline Cyan
$Locatie = Read-host
$Result = Test-Path $Locatie}

Write-host "NK2edit wordt uitgevoerd"
 .\NK2Edit.exe /nobackup /nofirstbackup /script .\Script.txt $Locatie
Start-Sleep -s 5
$Testresult = ((Get-date) - (GI $locatie ).LastWriteTime).Seconds
While ($Testresult -ne 15){ $Testresult = ((Get-date) - (GI $locatie ).LastWriteTime).Seconds
}
Write-host "NK2Edit is klaar"

$Choice = ""
While ($choice -notmatch "[y|n]"){$choice = read-host "Wilt u dit voor nog een user uitvoeren? (Y/N)"}
[console]::ResetColor()	
}

I compressed the script and NK2edit in a zip file. Click here to download.

Resolving script for autocomplete

A servicedesk employee came to me with a problem with the autocomplete functionality in Outlook 2010. So I looked around for a bit to find some code. I came across a script that does the job. I added functionality so that it would work for the servicedesk. For the script to work, the user will need to have enough permissions to add permissions to any mailbox. The script is semi-automatic. So user interaction is needed.

Outlook 2003 used to have NK2 files which are corrupt from time to time. Then Microsoft replaced that functionality into a .dat with exact the same issues. Clearly in Outlook 2010 this problem still exists. The key difference is that Outlook 2010 and Outlook 2013 in combination with Exchange 2010 or later has a functionality called suggested contacts. This works similar to the autocomplete function only better. Contacts added to the suggested contacts will be in the autocomplete list. Imagine that by generating a list of all unique recipients in the sent item you’ll have a list that is satisfactory for the user.

Furthermore this functionality works regardless of the user profile. So in our case if a user uses Outlook Web App that user will also have that same functionality. There is a catch. This does not work as intended in combination with Res Workspace Manager and Zero-profiling. To get this right you can use another script.

This script gives write the LegacyDN as output on screen. This is needed for situaties where you have GAL segregation. Then it opens the control panel for e-mail on that server. Of course Outlook needs to be installed on the system. That you need to permit the script to use Outlook. You’ll get the warning as seen below:

Screen Shot 2015-04-16 at 13.55.28

Again if you use RES Workspace Manager with Zero-Profiling use another script.

#Import Modules,Snapins and functions
ipmo ActiveDirectory
Add-PSSnapin Microsoft.Exchange.Management.Powershell.E2010
Function AddTo-SuggestedContactsIfNotAlreadyAdded ($name, $email)
{

    if(($name -eq "") -or ($email -eq "") -or ($name -eq $null) -or ($email -eq $null)){
        return;
    }

    $name = $name.Replace("'", "").Replace("""", "")


    $contactAlreadyAdded = $false

    foreach ($elem in $global:alreadyAddedEmails) {
        if(($elem.ToLower() -match $email.ToLower())){
            $contactAlreadyAdded = $true
            Write-Host  ($global:counter)"/"($totalItems)  "SKIPPED " $name.PadRight(25," ") "-" $email
            return;
        }
    }

    if(!$contactAlreadyAdded )    {
        $newcontact = $contacts.Items.Add()
        $newcontact.FullName = $name
        $newcontact.Email1Address = $email
        $newcontact.Save()
        $global:alreadyAddedEmails += $email
        Write-Host ($global:counter)"/"($totalItems)  "ADDED   " $name.PadRight(25," ") "-" $email
    }
}

#Set Variables
$Admin = [Environment]::UserName
$choice2 = "y"
$Admindomain = $env:userdomain

#Set loop function
while ($choice2 -match  "y"){

#Request Username from interactive User
Write-Host "Voer de gebruikersnaam van de gebruiker in: " -NoNewline -F Cyan 
$Username = read-host 

#Test if user exist
$Testresult = $Null
$Testresult = Get-ADUser $Username -Properties *
While ($Testresult -eq $Null){
write-host "De opgegeven Username bestaat niet of is foutief ingevoerd. Toets de username in in het format zoals bijvoorbeeld OTHSBE02" -b Black -f Red
$Username = Read-Host "Voer de gebruikersnaam van de gebruiker in:" -NoNewline -F Cyan 
$Testresult = Get-ADUser $Username -Properties *}

Get-ADUser -Identity $Testresult.samaccountname -Properties legacyExchangeDN  | Select legacyExchangeDN

#Addmailbox permission to the admin user with retry
Add-MailboxPermission -Identity $Testresult.samaccountname -AccessRights "FullAccess" -User ("$AdminDomain"+"\"+"$Admin")
$Testresult2 = $null
$Testresult2 = Get-MailboxPermission -Identity $Testresult.samaccountname | ? {$_.User -match ("$AdminDomain"+"\\"+"$Admin")}
While ($Testresult2 -eq $Null){
write-host "De rechten op de mailbox zijn niet goed gezet. Het wordt nogmaals geprobeerd" -b Black -f Red
Add-MailboxPermission -Identity $Testresult.samaccountname -AccessRights "FullAccess" -User ("$AdminDomain"+"\"+"$Admin")
$Testresult2 = Get-MailboxPermission -Identity $Testresult.samaccountname | ? {$_.User -match ("$AdminDomain"+"\\"+"$Admin")}}


#Opens Mail control panel
C:\Windows\SysWOW64\control.exe mlcfg32.cpl

#Confirm that the admin have changed to Exchange profile accordingly.
$choice = $Null
 while ($choice -notmatch "[y|n]"){
     $choice = read-host "Heb je het Outlook profiel aangepast? (Y/N)"
      }


$outlook = new-object -com outlook.application
$olFolders = "Microsoft.Office.Interop.Outlook.OlDefaultFolders" -as [type]
$namespace = $outlook.GetNameSpace("MAPI")
$sentItems = $namespace.getDefaultFolder($olFolders::olFolderSentMail)
$alreadyAddedEmails = @() #Empty Array
$counter = 0;
$totalItems = $sentItems.items.count;

Write-Host "Scanning through" $totalItems "emails in SentItems"

$contacts = $outlook.Session.GetDefaultFolder($olFolders::olFolderSuggestedContacts)



# Loop through all emails in SentItems
$sentItems.Items | % {

    #Loop through each recipient
    $_.Recipients | %{
        AddTo-SuggestedContactsIfNotAlreadyAdded $_.Name  $_.Address
    }
    $global:counter = $global:counter + 1
}

$outlook.Quit()

#Addmailbox permission to the admin user with retry
Remove-MailboxPermission -Identity $Testresult.samaccountname -AccessRights "FullAccess" -User ("$AdminDomain"+"\"+"$Admin")
$Testresult2 = Get-MailboxPermission -Identity $Testresult.samaccountname | ? {$_.User -match ("$AdminDomain"+"\\"+"$Admin")}
While ($Testresult2 -ne $Null){
write-host "De rechten op de mailbox zijn niet goed gezet. Het wordt nogmaals geprobeerd" -b Black -f Red
Remove-MailboxPermission -Identity $Testresult.samaccountname -AccessRights "FullAccess" -User ("$AdminDomain"+"\"+"$Admin")
$Testresult2 = Get-MailboxPermission -Identity $Testresult.samaccountname | ? {$_.User -match ("$AdminDomain"+"\\"+"$Admin")}}


#Present user with the choice to do another user.
$choice2 = read-host "Wilt u dit voor nog een user uitvoeren? (Y/N)"
}

 

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