Five Quick Tasks Made Easy with PowerShell

Note: an edited version of this story ran on Computerworld.com on December 13, 2017.

I’ve written a lot about PowerShell on this site, but my favorite thing to do is show how to apply the scripting language to various tasks you already have to do as part of your regular role and responsibility. In this piece I’ll take five common administrative tasks that with a GUI would take time and be rote and boring and I’ll show you how to script them using PowerShell. Without further ado, let’s get started.

One – Checking for the presence of patches with PowerShell

As of this writing, the Petya ransomware (well, it is not exactly ransomware since there is no evidence that one could ever recover the files that the worm actually deletes rather than encrypts, as it lets on in its user communications during execution) has disabled most of the IT assets in the country of Ukraine and locked out several global financial and logistics firms from much their own hardware and software. In fact I am an unwitting participant—my e-mail address has been mistakenly included—in an emergency e-mail chain from employees in a very large company that are desperate to communicate with each other using their personal addresses and accounts to keep their business running. This is a very disruptive time.

What’s more problematic is that the vulnerability that this malware uses to enter was patched three months ago. Hopefully your enterprise has a robust patch management platform with all the resources that you need to patch regularly, consistently, and early enough to not be victims. But apparently that hope is just that, as that e-mail thread and hundreds of news reports confirm. So how can we leverage the power of PowerShell to look for at least the missing patch that would mitigate this particular malware variant?

PowerShell supports looking for hotfixes, of course, via the Get-Hotfix cmdlet. Let’s build on this concept to see how we might get a quick and dirty survey of our patching situation going. First, we can use Get-Hotfix on a single computer. Use the –ID parameter, and for its value, enter the Knowledge Base number corresponding with the patch. For reference, the patch that mitigates this most recent ransomware:

  • Windows 7 / Windows Server 2008: KB4012212
  • Windows 8 / Windows Server 2012: KB4012217, KB4015551, KB4019216
  • Windows 10 / Windows Server 2012 R2: KB4012216, KB4015550, KB4019215
  • Windows Server 2016: KB4013429, KB4019472, KB4015217, KB4015438, KB4016635

On a Windows  7 system, we would use:

Get-hotfix –id KB4012212

You can also execute the command against remote computers by using the –ComputerName parameter

Get-hotfix –id KB4012212 –computername Jon-Desktop

You can put a list of computer names in a variable and then pull all of those hotfix installation reports with a couple lines of PowerShell scripting:

$computerstocheck = @(“JON-NUC”,”SERVER”)

ForEach ($computer in $computerstocheck)  {

Get-Hotfix –id KB4012212 –ComputerName $computer

}

Here is a sample of the output from that particular script run on my deployment:

Two – Disabling vulnerable versions of SMB with PowerShell

If you need a “too long; didn’t read” version of this next section, consider this: “you’re stupid if you are still running SMB version 1. Turn it off.”

That is harsh, but it is appropriately so. SMB 1 is decades old file sharing technology that has been vastly improved, both in terms of feature set, performance, and security, by later versions of SMB. There is no practical reason for enterprises running any modern version of Windows to be running SMB1. While some Windows alternatives like Linux and SAMBA emulate SMB1, on a Windows system from the last decade or more, SMB1 gives you nothing and takes away significantly from your overall security posture. SMB1, in fact, is one of the key ways that the Petya and WannaCry malware exploited vulnerable systems. (The patch fixed the vulnerability but SMB1 was essentially the channel, or the vehicle, the vulnerability used.)

On Windows 8, Windows 8.1, and Windows Server 2012 and Windows Server 2012 R2, there is a built in cmdlet that deals with the SMB versions present and in operation on any given machine. It is called Set-SMBServerConfiguration. You probably will not need to use this very often, so I will just give you the command you need to turn SMB1 off:

Set-SmbServerConfiguration -EnableSMB1Protocol $false

On those systems, the change is effective immediately. This will kill WannaCry and Petya in their footsteps, although deploying the patch I mentioned in the previous section is still highly recommended.

Earlier versions of Windows are a little more complicated. Windows XP did not have PowerShell built in and you have bigger problems than SMB1 if you are still running Windows XP in production, so I will start with Windows Vista in 2006. For Vista, Server 2008, Windows Server 2008 R2, and Windows 7, you will need to use PowerShell to change the properties of a Registry item. The following will get it done:

Set-ItemProperty -Path “HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters” SMB1 -Type DWORD -Value 0 -Force

As with most Registry changes, you will need to reboot the affected boxes before the operating system will pick up the change. You can choose to push this PowerShell script out in a login script, or execute it remotely with PSExec, or even use your own software distribution system to make sure this runs. For that matter, you could even use Group Policy Preferences to make the change, but then you wouldn’t be using PowerShell!

Three – Monitoring the success or failure of Windows Server Backup attempts

One thing that helps secure your integrity when it comes to ransomware infections is the ability to recover from them. Obviously it would be great to never be infected by ransomware, but that is not always possible: users will be users. But if you can restore from backups within an hour or two and get everyone going, you look like a hero and your business has not lost much. The key here, however, is to have consistent backups, and the best way to make something consistent is to systematize it. How can we use PowerShell, then, to make sure our backup system is working as intended and more importantly to know when backups have failed?

The inspiration for this script came from Titus1024 over on Spiceworks [https://community.spiceworks.com/how_to/138237-useful-powershell-scripts-commands], though I’ve edited his script and improved it for clarity. What it does is grabs today’s date and stores it in a variable, grabs the backup status from the Get-WBSummary cmdlet and stores its output in a variable, and then gets the date of the last backup attempt, the last successful backup, all successful backups, and the last error message for an unsuccessful backup. Then it uses some comparison logic to fire off an e-mail message with the appropriate status.

A few things to note:

  • In Windows Server 2008 R2, you will need not only the Windows Server Backup role installed, but also the command line tools feature—that is what gets you the requisite PowerShell commands.
  • Before deploying the script into production, you will want to change the email from and to destinations as well as specifying a proper SMTP server through which you can transmit these outbound messages.
  • And also do not forget both closing curly braces; they are important and the script will not work without them.

$Date=Get-Date; $Date=$Date.AddDays(-1).ToShortDateString()
$WBSummary=Get-WBSummary
$ResultOfLastBackup=$WBS.LastBackupResultDetailedHR
$TimeOfLastSuccessfulBackup=$WBS.LastSuccessfulBackupTime.ToShortDateString()
$LastBackup=$WBSummary.LastBackupTime
$BackupsAvailable=$WBSummary.NumberOfVersions
$ErrorMessage=Get-WBJob -Previous 1; $ErrorMessage=$ErrorDesc.ErrorDescription

$SuccessfulBackupEmailMessageBody=@”
Date: $TimeOfLastSuccessfulBackup
Backups Available: $BackupsAvailable
“@

$FailedBackupEmailMessageBody=@”
Last Backup: Failed
Date: $LastBackup
Reason: $ErrorMessage
“@

$RecurringFailedBackupEmailMessageBody=@”
Last Backup: Failed
Date: $LastBackup
Last Successful Backup: $TimeOfLastSuccessfulBackup
Reason: $ErrorMessage
“@

 

if($ResultOfLastBackup -eq 0){

Send-MailMessage -To “someone@yourdomain.com” -From “noreply@awesomesystemadmin.org” -Subject “Backup Successful – $ENV:COMPUTERNAME” -Body $SuccessfulBackupEmailMessageBody -SmtpServer smtp.gmail.com
}
elseif($ResultOfLastBackup -ne 0 -or $TimeOfLastSuccessfulBackup -lt $Date){
if($TimeOfLastSuccessfulBackup -lt $Date){
Send-MailMessage -To “someone@yourdomain.com” -From “noreply@awesomesystemadmin.org” -Subject “ALERT to a Failed Backup Attempt – $ENV:COMPUTERNAME” -Body $RecurringFailedBackupEmailMessageBody -SmtpServer smtp.gmail.com
}
else{
Send-MailMessage -To “someone@yourdomain.com” -From “noreply@awesomesystemadmin.org” -Subject “ALERT to a Failed Backup Attempt – $ENV:COMPUTERNAME” -Body $FailedBackupEmailMessageBody -SmtpServer smtp.gmail.com
}
}

Four – Monitor the membership of the Domain Administrators group for irregularities and changes

One of the key intrusion detection responsibilities you have as an administrator is to watch for privilege escalation attacks. As such, it makes sense to understand who all should be members of the Domain Admins group and then get updated if that list changes so you can make sure the modification is kosher. PowerShell can help us do this.

The first step is to understand what the baseline membership looks like. This command grabs that information:

Get-ADGroupMember -Server yourdomain.tld -Identity “Domain Admins”

From that list, we want to get the login IDs – a property known as the SAMAccountName – and then we want to flush that list out. We could export to text, but let’s instead use XML, which is a little more flexible to work with in case you want to expand out the powers of the script later. (Reading text back in can be done, but manipulating a bunch of strings is a lot tougher.)

Get-ADGroupMember -Server yourdomain.tld -Identity “Domain Admins” |

Select-Object -ExpandProperty samaccountname |

Export-Clixml -Path ‘C:\powershell\domainadminbaseline.xml’

Now, let’s declare some variables: today’s date, the path to that baseline XML and its file hash , the path to a new XML file that gets created whenever you will run this script, and a placeholder variable.

$HashOfBaselineAdmins = Get-FileHash -Path ‘c:\powershell\domainadminbaseline.xml’ |

Select-Object -expandProperty Hash

$Date = Get-Date

$PathToCurrentAdmins = ‘c:\powershell\domainadmintest.xml’

$Delta = ”

Next, run the Get-ADGroupMember command again to get the current results of the group membership.

Get-ADGroupMember -Server yourdomain.tld -Identity ‘Domain Admins’ |

Select-Object -ExpandProperty samaccountname |

Export-Clixml -Path $PathToCurrentAdmins -Force

Then, let’s get the hash of that new file and store it in a variable we can use for comparison.

$HashOfCurrentAdmins = Get-FileHash -Path $PathToCurrentAdmins | Select-Object -expandProperty Hash

Now we run some comparisons.

If ($HashOfCurrentAdmins -ne $HashOfBaselineAdmins){

$Delta = ‘Yes’

$WriteChangesMessage = ‘Domain Admins membership change noted on: ‘ + $date

$WriteChangesMessage | Out-File -FilePath ‘C:\powershell\changenoted.txt’ -Append -Force

} else {

 

$Delta = ‘No’

$WriteNoChangesMessage = ‘Domain Admins membership is the same as it was as of: ‘ + $Date

$WriteNoChangesMessage | Out-File -FilePath ‘C:\powershell\nochange.txt’ -Append -Force

}

Now we can add some logic that if that delta variable is  Yes, a mail message should be created and sent to you.

If ($Change -eq ‘Yes’) {

Send-MailMessage -From noreply@awesomesystemadmin.org -to someone@yourdomain.com -Subject ‘Domain Admins group membership change detected’ -Body ‘A change in the membership of the Domain Admins group has been noted.’ -Attachments $PathToCurrentAdmins

}

Hat tip to David Hall [http://www.signalwarrant.com/2017/06/28/hey-powershell-text-me-if-my-domain-admins-group-changes/] for his work here, which I’ve again edited and clarified.

Five – Make PowerShell instruct your computer to talk to you

This trick is a little less, well, technical, than the other four tips I’ve covered in this piece, but it has a wide range of applications. Here I’ll show you the three lines of PowerShell code needed to make your computer recite something to you.

First, you need to add the prerequisite ingredients:

Add-Type –AssemblyName System.Speech

Next, declare a variable which will essentially create a .NET object whose function you will call later. This just saves you some typing; consider it like a shortcut. We will call the variable $talk.

$talk = New-Object –TypeName System.Speech.Synthesis.SpeechSynthesizer

Now, to get something to come out of your speakers, you just call the Speak property of your $talk .NET object.

$talk.Speak(‘Now is the time for all good men to come to the aid of their country.’)

The great thing about this trick is that you can add it basically into any script you already have so that you can be alerted audibly if something is off, or when a lengthy compare process is done, or you want to announce something to your users (stick this in a login script, for example). I might choose to include an audible alert to announce that membership in my domain admins group has changed—see the previous example—or I might want to list a group of computers that does not have a critical patch installed. The sky is really the limit here, and all you need to add this functionality is essentially these three lines of PowerShell code.

Please note: I reserve the right to delete comments that are offensive or off-topic.

Leave a Reply

Your email address will not be published. Required fields are marked *