PowerShell primers

5 more Windows admin tasks made easy with PowerShell

From checking systems to see if security patches are installed to monitoring Windows Server Backup attempts, PowerShell scripts can make tedious IT administrative tasks quick and easy.

14 check your device manager

PowerShell primers

Show More

I’ve written a lot about Microsoft 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 responsibilities. In a previous article, I demonstrated how to accomplish five common administrative tasks using PowerShell.

In this piece I’ll take five more IT administrative tasks (this time mostly security-related) that with a GUI would be slow and boring and show you how to script them using PowerShell. 

1. Check for the presence of patches with PowerShell

In June 2017, the Petya/NotPetya malware disabled most of the IT assets in the country of Ukraine and spread worldwide, locking out several global financial and logistics firms from much their own hardware and software. I had a first-hand view of the chaos that ensued: My email address was mistakenly included in an emergency email chain from employees in a very large company who were desperate to communicate with each other using their personal addresses and accounts to keep their business running. It was a very disruptive time.

What’s more problematic is that the vulnerability that this malware uses to enter systems — the very same vulnerability exploited by May’s WannaCry ransomware attack — was patched in March 2017, two months before the WannaCry outbreak and three months before the Petya/NotPetya outbreak. I hope your enterprise has a robust patch-management platform with all the resources you need to patch regularly, consistently, and early enough to avoid being a victim. But apparently that is not the case at many firms, as that email thread and hundreds of news reports confirm.

So how can we leverage PowerShell to look for the patch that would mitigate this particular malware variant? PowerShell supports looking for hotfixes 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. 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, these are the patches that mitigate the Petya 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

So, for instance, on a Windows 7 system, we would use:

Get-Hotfix –id KB4012212

And to check for the multiple hotfixes we’d need on a Windows 8 machine:

Get-Hotfix –id KB4012217,KB4015551,KB4019216

Note that I just separated multiple hotfixes I’m inquiring about with a single comma and no space in between. 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, which indicates that the hotfixes are indeed installed, and the date and time they were installed:

Microsoft PowerShell check for hotfix installation Jonathan Hassell / IDG

(Click image to enlarge it.)

If the script returns an error, then you know you need to install those patches.

Even though the big Petya/NotPetya outbreak was a few months ago, ransomware remains a growing threat, and you can use these same techniques upon the next outbreak once Microsoft releases the relevant updates. The Microsoft Security Response Center is the best place to look for updates as they come out in response to malware outbreaks.

2. Disable vulnerable versions of SMB with PowerShell

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

That is harsh, but appropriately so. Server Message Block version 1 (abbreviated SMBv1 or SMB1) is decades-old file-sharing technology that has been vastly improved 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 patches for those exploits fixed the vulnerability, but SMB1 was essentially the channel, or the vehicle, that the vulnerability used.)

On Windows 8 and Windows Server 2012 systems, 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

For Windows 8.1, Windows 10, Windows Server 2012 R2 and Windows Server 2016 systems, run the following command instead:

Disable-WindowsOptionalFeature -Online -FeatureName SMB1Protocol

On those systems, the change is effective immediately. This will stop WannaCry and Petya in their tracks, 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 2001’s Windows XP in production, so I will start with Windows Vista, introduced in 2006. For Windows Vista, Windows 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!

3. Have PowerShell monitor 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 again, 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 on Spiceworks, 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 email 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. In later versions of Windows Server, just the Backup role is required.
  • Before deploying the script into production, you will want to change the placeholder 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()
$ErrorMessage=Get-WBJob -Previous 1; $ErrorMessage=$ErrorDesc.ErrorDescription

Date: $TimeOfLastSuccessfulBackup
Backups Available: $BackupsAvailable

Last Backup: Failed
Date: $LastBackup
Reason: $ErrorMessage

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

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



4. Have PowerShell 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 — those in which a hacker exploits flaws in a system to gain access to increasingly high-level administrative privileges. As such, it makes sense to understand exactly who should be members of the Domain Admins group and get updated if that list changes so you can make sure the modification is kosher. PowerShell can help. (Hat tip to David Hall for his tip on SignalWarrant, which I’ve again edited and clarified.)

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 take that information and send it to another file. 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 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


5. Have 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('Hello. Please take me to your leader.')

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.

Copyright © 2017 IDG Communications, Inc.

It’s time to break the ChatGPT habit
Shop Tech Products at Amazon