Powershell: Why Did I Resist?

A good deal of any system administrators job is automation. Even when you’re working in small environments doing the same thing on every user’s machine individually is needlessly tiresome and always error prone. My current environment has well over 400 servers and at least 1000 desktops so anything that needs to touch all of them has to be automated, there’s just no other option. In the past VBScript was the be all and end all of Windows based scripting and is still used as the de facto automation language for many IT shops today. However with the coming of Vista and Server 2008 we saw the introduction of a plucky new tool called Powershell (first seen in the wild in 2006) which looked to be the next greatest thing for automating your IT environment. Due to Vista’s poor reception and by association Server 2008 Powershell didn’t really take off that well. In fact I’d actively ignored it up until about 6 months ago when I started looking at it more closely as a tool to automate some VMware tasks. Little did I know then that this new world of Powershell would soon make up the majority of my day to day work.

Now the developers out there will know that Visual Basic (VB) is somewhat of a beginner’s programming language. Sure it’s feature complete when compared to its bigger brother C# however it’s rather lax with its standards and this makes any code done in VB rather inelegant. This was probably why I shied away from Powershell initially as I thought it would just be an evolutionary step from VBScript, but I couldn’t have been more wrong. The syntax is decidedly closer to C# than VB although the legacy of behind the scenes tricks to hide some complexities from its users is still there, although with the added benefit of those small tricks being available should you know where to look. Additionally the ease of integration with other Microsoft coding platforms (like loading .NET dlls) is absolutely amazing, giving you the power of doing almost anything you can with their other languages right there in your script.

The real kicker though is the shift in focus that Microsoft has taken when it implemented Powershell all those years ago. Typically their infrastructure products like Exchange or System Center were either built by separate teams or came from another company that Microsoft had purchased. This meant that there was no standard way of interfacing with these products making automation a real pain, usually ending up with you having to use a third party tool or write reams of VBScript. For most future releases however Microsoft has built their management tools on top of Powershell, meaning that any action performed in the management consoles can be replicated via a script. This was most obvious when they released Exchange 2007 and any command you performed on the GUI would show you the Powershell command that it ran.

To show you how much you can do with Powershell I’m going to include 2 of my own scripts which I invested quite a bit of time in. The first shown below is a script that will scan your domain and alert you when someone adds themselves to the Domain Administrators group:

$domainAdmins = dsget group “CN=Domain Admins,CN=Users,DC=your,DC=domain,DC=com” -members -expand
$list = Get-Content C:\Directory\Where\Script\Runs\DomainAdminsList.txt

$mail = new-object System.Net.Mail.MailMessage
$mail.From = new-object System.Net.Mail.MailAddress(“[email protected]“)
$mail.To.Add(“[email protected]“)
$smtpserver = “YourSMTPServer
$mail.Subject = “Unauthorized Domain Administrator Priveleges Detected.”
$smtp = new-object System.Net.Mail.SmtpClient($smtpserver)

foreach ($domainAdmin in $domainAdmins)
{
$found = $false
foreach ($line in $list)
{
if ($domainAdmin -eq $line){$found = $true}
}

if ($domainAdmin -eq “”){$found = $true}

if($found){}
else
{
$date = Get-Date
$hostname = hostname
Write-Host $domainAdmin “not found in control file.”
$mail.Body = $domainAdmin + ” not found in control file. Script run on ” + $hostname +” at ” + $date + ” using control file C:\Directory\Where\Script\Runs\DomainAdminsList.txt
$smtp.Send($mail)
}
}

You’ll want to first run “dsget group “CN=Domain Admins,CN=Users,DC=your,DC=domain,DC=com” -members -expand | DomainAdminsList.txt” to generate the text file of domain admins. Once you’ve done that you can schedule this to run say every hour or so and you’ll get an email when someone gives an account domain administrator. You can modify this for any group to, just update the first line with the CN of the group you want to scan.

The second is one that I’m quite proud of, it will tell you when someone changes a group policy in your domain. Pretty handy for when you’ve got a bunch of developers who have access to do that and routinely break other people’s systems when they do. You’ll need to grab the ListAllGPOs.ps1 script from here first (although I called it GPOList.ps1):

$GPOs = .\GPOList.ps1 -query -verbose -domain your.domain.com

$DCs = “DC01″,”DC02”

$baseline = Import-Csv GPOBaseline.csv

$outFile = “D:\Apps\Scripts\GPOScanner\Output.txt”
$outBody = “D:\Apps\Scripts\GPOScanner\OutBody.txt”
$null | Out-File $outFile
$null | Out-File $outBody
$emailRequired = $false

Write-Host “Scanning your.domain.com
your.domain.com” | Out-File $outFile -append
foreach ($cGPO in $devGPOs)
{
$found = $false
foreach ($bGPO in $baseline)
{
if ($cGPO.ID -match $bGPO.ID)
{
$found = $true

if ($bGPO.ModificationTime.Equals($cGPO.ModificationTime.ToString()))
{}
else
{
$output = “WARNING: GPO ” + $cGPO.Displayname + ” has been modified since baseline.”
Write-Host $output
$output | Out-File $outBody -append
$output = “Modification time: ” + $cGPO.ModificationTime + “”
Write-Host $output
$output | Out-File $outBody -append
$emailRequired = $true

$cGPO.ModificationTime.AddSeconds(1).ToString()
foreach ($dc in $DCs)
{
$dc
$logs = [System.Diagnostics.EventLog]::GetEventLogs($dc)
foreach($log in $logs)
{
if($log.LogDisplayName -eq “Security”)
{
$entries = $log.Entries
foreach($entry in $entries)
{
if ($entry.EventID.Equals(4663) -or $entry.EventID.Equals(4656) -or $entry.EventID.Equals(560))
{
if ($entry.Message.Contains($cGPO.ID))
{
$entry | fl
$entry | fl | Out-File $outFile -append
}
}
}
}
}
}
}
}
}

if ($found -eq $false)
{
$emailRequired = $true
$output = “New GPO ” + $cGPO.DisplayName + ” not found in baseline.”
Write-Host $output
$output | Out-File $outBody -append
}
}

if ($emailRequired)
{
$hostname = hostname
$date = Get-Date
$output = “Script was run on ” + $hostname + ” at ” + $date + ” using control files located in D:\Apps\Scripts\GPOScanner. Please see the attachment for related event log information.”
$output | Out-File $outBody -append
$mail = new-object System.Net.Mail.MailMessage
$mail.From = new-object System.Net.Mail.MailAddress(“[email protected]“)
$mail.To.Add(“[email protected]com”)
$smtpserver = “YourSMTPServer
$mail.Subject = “Group Policy Changes Detected.”
$smtp = new-object System.Net.Mail.SmtpClient($smtpserver)
$mail.Body = Get-Content $outBody
$att = new-object Net.Mail.Attachment($outFile)
$mail.Attachments.Add($att)
$smtp.Send($mail)
$att.Dispose()
}

Again you’ll want to run “.\GPOList.ps1 -query -verbose -domain your.domain.com | Export-Csv GPOBaseline.csv” to generate the baseline. This script will first look for any changes then scour the security logs of your domain controllers to find who did it, sending you the logs of who changed it and when. Pretty neat eh?

$GPOs = .\GPOList.ps1 -query -verbose -domain your.domain.com

$DCs = “DC01″,”DC02”

$baseline = Import-Csv CENTRALGPOBaseline.csv

$outFile = “D:\Apps\Scripts\GPOScanner\Output.txt”
$outBody = “D:\Apps\Scripts\GPOScanner\OutBody.txt”
$null | Out-File $outFile
$null | Out-File $outBody
$emailRequired = $false

Write-Host “Scanning your.domain.com”
“your.domain.com” | Out-File $outFile -append
foreach ($cGPO in $devGPOs)
{
$found = $false
foreach ($bGPO in $baseline)
{
if ($cGPO.ID -match $bGPO.ID)
{
$found = $true

if ($bGPO.ModificationTime.Equals($cGPO.ModificationTime.ToString()))
{}
else
{
$output = “WARNING: GPO ” + $cGPO.Displayname + ” has been modified since baseline.”
Write-Host $output
$output | Out-File $outBody -append
$output = “Modification time: ” + $cGPO.ModificationTime + “”
Write-Host $output
$output | Out-File $outBody -append
$emailRequired = $true

$cGPO.ModificationTime.AddSeconds(1).ToString()
foreach ($dc in $DCs)
{
$dc
$logs = [System.Diagnostics.EventLog]::GetEventLogs($dc)
foreach($log in $logs)
{
if($log.LogDisplayName -eq “Security”)
{
$entries = $log.Entries
foreach($entry in $entries)
{
if ($entry.EventID.Equals(4663) -or $entry.EventID.Equals(4656) -or $entry.EventID.Equals(560))
{
if ($entry.Message.Contains($cGPO.ID))
{
$entry | fl
$entry | fl | Out-File $outFile -append
}
}
}
}
}
}
}
}
}

if ($found -eq $false)
{
$emailRequired = $true
$output = “New GPO ” + $cGPO.DisplayName + ” not found in baseline.”
Write-Host $output
$output | Out-File $outBody -append
}
}

if ($emailRequired)
{
$hostname = hostname
$date = Get-Date
$output = “Script was run on ” + $hostname + ” at ” + $date + ” using control files located in D:\Apps\Scripts\GPOScanner. Please see the attachment for related event log information.”
$output | Out-File $outBody -append
$mail = new-object System.Net.Mail.MailMessage
$mail.From = new-object System.Net.Mail.MailAddress(“[email protected]”)
$mail.To.Add(“[email protected]”)
$smtpserver = “YourSMTPServer”
$mail.Subject = “Group Policy Changes Detected.”
$smtp = new-object System.Net.Mail.SmtpClient($smtpserver)
$mail.Body = Get-Content $outBody
$att = new-object Net.Mail.Attachment($outFile)
$mail.Attachments.Add($att)
$smtp.Send($mail)
$att.Dispose()
}

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.