Tag Archives: Administration

Find where a certificate is installed

Some organizations use wildcard certificates. The problem with these certificates is that they start to lead a life on it’s own. So when the time comes that such a certificate is about to expire one can wonder where is this certificate installed? Luckily we have Powershell to save us from having to open all certificate stores on all servers in the domain. At first I was  thinking maybe the easiest way could be to have a Powershell remoting session. But that would require all servers to have Powershell remoting enabled. Unfortunately not all servers have it enabled or have a reasonable Powershell version to start with. So just for documenting purpose I’ll write down the command to find a certificate.

A certificate has a thumbprint. This thumbprint is equal on all servers as it part of the certificate, much like the serial number.  So you can easily find the thumbprint where your looking for running the command on a server where the certificate is present.

Just copy the thumbprint and enter it into the command to find the certificate like the on below

Invoke-Command {gci cert:\Localmachine\My | ? {$_.Thumbprint -eq "FF9C130AE6C1D18FAC43AF0416E44B7011307545"}} -ComputerName #COMPUTERNAME#

Replace #COMPUTERNAME#  for the computername you would want to question. Now this is all good but like I mentioned before I would want to find certificate regardless of Operation system contstrains or whether Powershell Remoting is enabled or not. So this script would process that accordingly. Change the thumbprint to the thumbprint of your certificate. The script export all servers where the certificate with the given thumbprint is present. Please feel free to modify this to your needs.

Function Get-Cert( $computer=$env:computername ){
    #Sets ReadOnly flag upon external Certificate store.
	$ro=[System.Security.Cryptography.X509Certificates.OpenFlags]"ReadOnly"
	#Opens LocalMachine store. You could potentially enter a different Cert store. 
	$lm=[System.Security.Cryptography.X509Certificates.StoreLocation]"LocalMachine"
	#Opens personal certificate store from local machine. 
	$store=new-object System.Security.Cryptography.X509Certificates.X509Store("\\$computer\My",$lm)
	#Establish connection with remote PC
    $store.Open($ro)

	#Display certificates in console or variable
	$store.Certificates

}

#Opens list
$Thumbprint = "FF9C130AE6C1D18FAC43AF0416E44B7011307545" 
$List = @()

#Servers reset their machine password within a specific timeframe
$Servers = Get-ADComputer -Filter * -Properties OperatingSystem,PasswordLastSet | ?{$_.OperatingSystem -match "Server" -and $_.PasswordLastSet -gt (Get-Date).AddDays(-60)}

Foreach ($Server in $Servers)
	{
	$Server.DNSHostName 
	If ((Get-Cert $Server.DNSHostname | ? {$_.Thumbprint -eq $Thumbprint}).Count -ne 0)
		{
		$List += $Server.DNSHostName
		}
	}

$List | % {AC -Value $_ -Path ".\ComputersWithCertificate.txt"}

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

Connecting to servers outside a domain using Powershell

From time to time you’ll find yourself running scripts to a server outside the domain. If you have the same credentials on that as on the domain there’s no problem. However in most cases you don’t. Normally you will have to encrypt the password as a secure string. However there are cases that such a solution costs to much time. Especially if you have multiple passwords for the same user account (eg. Administrator). So to get this done you might use a old trick to connect to the server using SMB en store the credentials session in the memory of the server running the script. I must admit that this is only if you’ll have to go quick and dirty. If’ve used it once and it may come in handy sometimes.

$Credentiallist = @()
$Credentiallist += "Password1"
$Credentiallist += "Password2"
$Credentiallist += "Password3"
$Credentiallist += "Password4"
$Errors = ".\Error.csv"
$Success = ".\Success.csv"
$net = new-object -ComObject WScript.Network
$ADComputers = Import-CSV ".\DNSNames.csv"
$Computers =@()
$ComputerSuccess =@()
$FailedComputers = @()
Foreach ($ADComputer in $AdComputers)
	{
	$ADComputer = $ADComputer.DNSHostName
	Foreach ($cred in $credentialList)
		{
		If ($ComputerSuccess -notcontains $ADComputer)
			{ 
			$net.MapNetworkDrive("u:", "\\$ADComputer\c$", $false, "localhost\Administrator", "$Cred")
			If((Test-Path "\\$ADComputer\C$\Windows" ) -eq $True)
				{
				$ComputerSuccess += $ADComputer
				#Do the code you want to run against the server
				}
			}
		}
	If ((get-psdrive -name "U" -Erroraction SilentlyContinue) -ne $null){$net.RemoveNetworkDrive("u:")}
	}

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

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.

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 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.

 

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!

Get all installed hot fixes and software

An adminstrator wanted to know if a certain KB from Microsoft had been installed. He wanted to know a command that does the job. In a world with DOS you can simply run:

systeminfo

However he also wanted to see if a Xenapp Hotfix had been installed. This of course can’t be done with the command above. So I wrote some code that exports the uninstall information from the local server. The code export x64 and x86 software. If the server is not 64 bit you’ll get errors. That said you’ll probably don’t have Powershell v3 installed on such server.

$List1 = Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*
$List1 += Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*
$List1 = $List1 | Select DisplayName, DisplayVersion, Publisher, InstallDate