Tag Archives: Powershell

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

GCI the funny way

Most recently I received a request from a user that wanted to know what folders are being used and what folders are not. We use Datadvantage for that so I sent him an export of the folders. But there is another way of doing that. You can do a GCI and count the number of slashes (\). You need to count the slashes to know at what level you need to do a Get-ChildItem -recurse. I exported a 3 level export as described in this post. Then at the last level I want to do a CGI  -recurse.

In my case it where 9 slashes. This differs per environment. You can count it by entering the following code at the last level

$Folder = "\\Folder\Path"
[string]$Test = $Folder
[int]$Result = $Test.Split("\").Count
Write-Host The number of levels to split on is $Result

If you know when to do a recursive CGI you can change the script below.

$path = "H:\Exports\ExportCGI.csv"
$list = import-csv $path
$Date = (Get-Date).AddDays(-400)
Foreach ($item in $list){[string]$Test = $item.folders
[int]$Result = $Test.split("\").count
If ($Result -lt "9"){$Result2 =@( gci $Item.Folders | Where {$_.LastWriteTime -gt $Date}); $Result2 = $Result2 | Sort LastWriteTime -Descending ; $OutInfo = $Item.Folders + ";" + $Result2[0].LastWriteTime ; AC -Value $Outinfo -Path "H:\Exports\ExportCGIPlusLastWriteTime.csv"}
If ($Result -ge "9"){$Result2 = @(gci $Item.Folders -Recurse | Where {$_.LastWriteTime -gt $Date}); $Result2 = $Result2 | Sort LastWriteTime -Descending ; $OutInfo = $Item.Folders + ";" + $Result2[0].LastWriteTime ; AC -Value $Outinfo -Path "H:\Exports\ExportCGIPlusLastWriteTime.csv"}
}

In My case I added a condition to only include the date if there was a file or folder that has been modified in the last 400 days. You may alter this in the way you want.

Have fun!

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.

 

Test whether PS Remoting is enabled

PSRemoting is a powerful way to manage remote computers. I often use this. However on some server PSRemoting is not enabled. A custom function can help you. When you import this in a Powershell session you can manage the server that don’t have PSRemoting enabled. I assume that you only have Windows Server 2008 and above servers in you domain.

First import the module or copy-paste it in you shell.

function Test-PSRemoting 
{ 
    param( 
        [Parameter(Mandatory = $true)] 
        $RemoteComputer 
    ) 
    
    try 
    {
		#Returns value 1 in a scriptblock on the remote computer
        $errorActionPreference = "Stop" 
        $result = Invoke-Command -ComputerName $RemoteComputer {1} 
    } 
    catch 
    { 
        Write-Verbose $_ 
        return $false 
    } 
     
    if($result -ne "1") 
    { 
        Write-Verbose "The remote computer: $RemoteComputer did not return the expected result. Please make sure you have enough privileges to connect to the computer." -f Red
        return $false 
    } 
    
    $true    
}

TestPSRemoting

Then to use it you can use the following code:

Import-Module ActiveDirectory
Import-Module ".\TestPSRemoting.psm1" -Force
$Servers = Get-ADComputer -Filter * -Properties OperatingSystem | Where {$_.OperatingSystem -match "Server"}
Write-host "There are " $Servers.count " servers found"
$DomainUser = ($env:userdomain+"\"+$env:username)
$secureString = Read-Host -AsSecureString "Enter your password"


Foreach ($Server in $Servers){
$Result = $null
$ServerName = $Server.Name
$Result = Test-PSRemoting $Server
If ($Result -eq $False){
Write-Host "PSRemoting will be enabled on server: $Server"
$Date = (Get-Date).AddMinutes(30)
schtasks /create /S $ServerName /RU $DomainUser /RP $secureString /SC Once /SD $date.ToString('dd"/"MM"/"yyy') /ST $date.ToString('HH":"mm') /TN "Enable PSRemoting" /TR "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe Enable-PSRemoting -Force" /F
}}

You need to be an Administrator of course or at least have enough permission to enable psremoting. If PSremoting is disabled an task will be scheduled to enable it within 30 minutes. You can enable PSRemoting remotely using the script. If you want to enable PSremoting on all workstations you’ll need to change the definition to something else. For example use a part of the DestinguishedName if an OU is called Workstations for example.

Good luck!