Automatically Create 40 Event Viewer Custom Views

I still find Custom Views useful when troubleshooting on individual workstations, and I’d recently been wondering if it was possible to push them out via GPP or similar. I started creating some views manually, as a test, but it was taking too long.

I’d recently been working on implementing Palantir’s WEF/WEC setup, and wondered whether I could leverage their legwork to automate the creation of these custom views.

The script I came up with took a fraction of the time to write, as opposed to the manual method. It does the following:

  1. Downloads the Palantir ‘windows-event-forwarding’ repo in ZIP format into a temporary folder
  2. Extracts the Event Log query out of each file in the ‘wef-subscriptions’ folder, and
    turns it into an appropriately-named custom Event Viewer view (XML) file in %PROGRAMDATA%\Microsoft\Event Viewer\Views

2017-11-07 16_51_46-Event Viewer

I love how simple PowerShell makes it to work with XML.

The script needs to be run as an admin in order to create the view files in %PROGRAMDATA%, unless you change the output path in the $templateStoragePath variable. It’ll also need to be able to connect to the Internet to download the ZIP file from GitHub.

I’ve started storing my scripts in my PowerShell GitHub repo rather than as Github Gists, and it’s harder to embed them on wordpress.com. View the code via the link below:

https://github.com/dstreefkerk/PowerShell/blob/master/Create-EventViewerCustomViews.ps1

Mitigate commodity malware attacks with Windows Firewall rules

There’s so much that can be done with the built-in Windows tools to prevent commodity malware or ransomware attacks before you even spend a cent on 3rd party tools. All of these things can (and should be) combined to create a good multi-layered strategy:

The last point has been on my to-do list for some time now. I was again reminded of it the other day while watching Sami Laiho’s recent Microsoft Ignite session about PAWs.

A lot of email-delivered malware begins with a macro or via DDE attack, and then attempts to connect to the Internet to pull down more nasties.

Today I came across this great blog post by Branden, in which he describes a handy method to prevent applications from communicating with hosts out on the Internet, while still allowing them to communicate within the internal network.

I set about manually creating a list of outbound firewall rules, including a whole bunch to mitigate the application whitelisting bypasses highlighted by the brilliant Casey Smith here. Doing this via the GUI is painful, and I wouldn’t wish it on anybody:

A listing of outbound firewall rules created in Windows Firewall with Advanced Security

Here’s a screenshot of PowerShell connecting to the web, before putting the firewall rule in place:

A PowerShell prompt, running Invoke-WebRequest to google.com, and showing a successful request

And here’s one taken after I enabled the firewall rule:

But PowerShell can still connect to an internal web server:

A PowerShell prompt, running Invoke-WebRequest against an internal HTTP server. Showing a successful response

There are obviously going to be exceptions to these rules, for example to enable your IT staff to access Azure AD or other cloud-based services via PowerShell, but those things should be done from dedicated administrative hosts anyway. This ruleset is more for the general user population.

When the time came to think about sharing this ruleset here on my blog, I discovered that it’s possible to export the rules from the registry and re-import them elsewhere, however that has its own potential issues.

I instead created the following PowerShell script that will generate all of the appropriate rules using the New-NetFirewallRule cmdlet. It’s also much easier to review this script to see what it does, rather than read a registry export file.

You could extend this script to apply the rules directly to the appropriate GPO by using the -GPOSession parameter on the New-NetFirewallRule cmdlet.

As usual, run at your own risk, and test thoroughly before deploying:

#Requires -Version 5 -Module NetSecurity -RunAsAdministrator
<#
.SYNOPSIS
Create-MitigationFirewallRules – Creates Windows Firewall rules to mitigate certain app whitelisting bypasses and to prevent command interpreters from accessing the Internet
.DESCRIPTION
A script to automatically generate Windows Firewall with Advanced Security outbound rules
to prevent malware from being able to dial home.
These programs will only be allowed to communicate to IP addresses within the private IPv4 RFC1918 ranges:
https://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces
The method I used to blacklist everything other than RFC1918 addresses was copied from a blog post by https://twitter.com/limpidweb
https://limpidwebblog.blogspot.com.au/2016/10/a-shower-leads-to-powershell-puking.html
Application Whitelisting bypasses sourced from Casey Smith's list here:
https://github.com/subTee/ApplicationWhitelistBypassTechniques/blob/master/TheList.txt
This script could be modified to write these rules to an existing GPO using the -GPOSession parameter on New-NetFirewallRule
PowerShell 5.0 is required because I'm using Classes
.OUTPUTS
Nothing
.EXAMPLE
Create-MitigationFirewallRules
.LINK
https://gist.github.com/dstreefkerk/800a9e0a22a6242a28b058be423cf0ba
.NOTES
Written By: Daniel Streefkerk
Website: http://daniel.streefkerkonline.com
Twitter: http://twitter.com/dstreefkerk
Todo: Nothing at the moment
Change Log
v1.0, 24/10/2017 – Initial version
#>
$rules = @()
Class FirewallRule {
[string]$DisplayName
[string]$Program
[string]$Description
[string]$Action = 'Block'
[string]$LocalAddress = 'Any'
[string]$Direction = 'Outbound'
[string[]]$RemoteAddress = @('0.0.0.0-9.255.255.255','11.0.0.0-172.15.255.255','172.32.0.0-192.167.255.255','192.169.0.0-255.255.255.255')
}
# 32 and 64 bit versions of cmd.exe
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – cmd.exe';Program='%SystemRoot%\SysWOW64\cmd.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – cmd.exe (x64)';Program='%SystemRoot%\System32\cmd.exe'}
# conhost.exe – not sure if this is needed, but blocking anyway
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – conhost.exe (x64)';Program='%SystemRoot%\System32\conhost.exe'}
# 32 and 64 bit versions of cscript.exe
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – cscript.exe';Program='%SystemRoot%\SysWOW64\cscript.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – cscript.exe (x64)';Program='%SystemRoot%\System32\cscript.exe'}
# 32 and 64 bit versions of wscript.exe
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – wscript.exe';Program='%SystemRoot%\SysWOW64\wscript.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – wscript.exe (x64)';Program='%SystemRoot%\System32\wscript.exe'}
# 32 and 64 bit versions of mshta.exe
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – mshta.exe';Program='%SystemRoot%\SysWOW64\mshta.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – mshta.exe (x64)';Program='%SystemRoot%\System32\mshta.exe'}
# PowerShell ISE
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – powershell_ise.exe';Program='%SystemRoot%\SysWOW64\WindowsPowerShell\v1.0\powershell_ise.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – powershell_ise.exe (x64)';Program='%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell_ise.exe'}
# PowerShell
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – powershell.exe';Program='%SystemRoot%\SysWOW64\WindowsPowerShell\v1.0\powershell.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – powershell.exe (x64)';Program='%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe'}
# 32 and 64 bit versions of regsvr32.exe – application whitelisting bypass
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – regsvr32.exe';Program='%SystemRoot%\SysWOW64\regsvr32.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – regsvr32.exe (x64)';Program='%SystemRoot%\System32\regsvr32.exe'}
# 32 and 64 bit versions of rundll32.exe – application whitelisting bypass
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – rundll32.exe';Program='%SystemRoot%\SysWOW64\rundll32.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – rundll32.exe (x64)';Program='%SystemRoot%\System32\rundll32.exe'}
# 32 and 64 bit versions of msdt.exe – application whitelisting bypass
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – msdt.exe';Program='%SystemRoot%\SysWOW64\msdt.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – msdt.exe (x64)';Program='%SystemRoot%\System32\msdt.exe'}
# .Net-based application whitelisting bypasses
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – dfsvc.exe – 2.0.50727';Program='%SystemRoot%\Microsoft.NET\Framework\v2.0.50727\dfsvc.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – dfsvc.exe – 2.0.50727 (x64)';Program='%SystemRoot%\Microsoft.NET\Framework64\v2.0.50727\dfsvc.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – dfsvc.exe – 4.0.30319';Program='%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\dfsvc.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – dfsvc.exe – 4.0.30319 (x64)';Program='%SystemRoot%\Microsoft.NET\Framework64\v4.0.30319\dfsvc.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – ieexec.exe – 2.0.50727';Program='%SystemRoot%\Microsoft.NET\Framework\v2.0.50727\IEExec.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – ieexec.exe – 2.0.50727 (x64)';Program='%SystemRoot%\Microsoft.NET\Framework64\v2.0.50727\IEExec.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – MSBuild.exe – 2.0.50727';Program='%SystemRoot%\Microsoft.NET\Framework\v2.0.50727\MSBuild.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – MSBuild.exe – 2.0.50727 (x64)';Program='%SystemRoot%\Microsoft.NET\Framework64\v2.0.50727\MSBuild.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – MSBuild.exe – 3.5';Program='%SystemRoot%\Microsoft.NET\Framework\v3.5\MSBuild.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – MSBuild.exe – 3.5 (x64)';Program='%SystemRoot%\Microsoft.NET\Framework64\v3.5\MSBuild.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – MSBuild.exe – 4.0.30319';Program='%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – MSBuild.exe – 4.0.30319 (x64)';Program='%SystemRoot%\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – InstallUtil.exe – 2.0.50727';Program='%SystemRoot%\Microsoft.NET\Framework\v2.0.50727\InstallUtil.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – InstallUtil.exe – 2.0.50727 (x64)';Program='%SystemRoot%\Microsoft.NET\Framework64\v2.0.50727\InstallUtil.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – InstallUtil.exe – 4.0.30319';Program='%SystemRoot%\Microsoft.NET\Framework\v4.0.30319\InstallUtil.exe'}
$rules += New-Object FirewallRule Property @{DisplayName='Block Internet Access – InstallUtil.exe – 4.0.30319 (x64)';Program='%SystemRoot%\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe'}
# Add more of your own rules by copying and uncommenting the line below
# $rules += New-Object FirewallRule -Property @{DisplayName='';Program=''}
# Create all of the rules using New-NetFirewallRule
foreach ($rule in $rules) {
New-NetFirewallRule DisplayName $rule.DisplayName Direction $rule.Direction Description $rule.Description Action $rule.Action `
LocalAddress $rule.LocalAddress RemoteAddress $rule.RemoteAddress Program $rule.Program
}

The embedded Github Gist doesn’t show up on mobile devices. Here’s a direct link to the raw script file: https://gist.githubusercontent.com/dstreefkerk/800a9e0a22a6242a28b058be423cf0ba/raw/c2be1189f88fb5ad9acaab708ad985587a576ceb/Create-MitigationFirewallRules.ps1

Automatically drop your Privileged Access Workstation off the network while it’s unattended

“One of my favorite hobbies is hunting sysadmins” – Hacker of Hacking Team’s network

I only periodically log in to my Privileged Access Workstation to carry out administrative tasks. Although I have restrictive policies applied and Windows Firewall locked down, there’s no reason for that machine to be on the network while I’m not actively using it.

In an attempt to address this, I created two simple scheduled tasks:

1. Disable all NICs when workstation is locked

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Date>2017-10-20T11:25:53.3600985</Date>
<Author>danielstreefkerk</Author>
<Description>This event disables all NICs when the workstation lock event (4800) is detected in the security log.
It won't work without Success auditing of Other Logon/Logoff events being enabled.</Description>
<URI>\Disable NIC(s) upon Workstation Lock</URI>
</RegistrationInfo>
<Triggers>
<EventTrigger>
<Enabled>true</Enabled>
<Subscription>&lt;QueryList&gt;&lt;Query Id="0" Path="Security"&gt;&lt;Select Path="Security"&gt;*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4800]]&lt;/Select&gt;&lt;/Query&gt;&lt;/QueryList&gt;</Subscription>
</EventTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<UserId>S-1-5-18</UserId>
<RunLevel>LeastPrivilege</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
<Arguments>-Command "Get-NetAdapter | Disable-NetAdapter -Confirm:$false"</Arguments>
</Exec>
</Actions>
</Task>

2. Enable all NICs when workstation is unlocked

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Date>2017-10-20T11:25:53.3600985</Date>
<Author>danielstreefkerk</Author>
<Description>This event enables all NICs when the workstation unlock event (4801) is detected in the security log.
It won't work without Success auditing of Other Logon/Logoff events being enabled.</Description>
<URI>\Enable NIC(s) upon Workstation Unlock</URI>
</RegistrationInfo>
<Triggers>
<EventTrigger>
<Enabled>true</Enabled>
<Subscription>&lt;QueryList&gt;&lt;Query Id="0" Path="Security"&gt;&lt;Select Path="Security"&gt;*[System[Provider[@Name='Microsoft-Windows-Security-Auditing'] and EventID=4801]]&lt;/Select&gt;&lt;/Query&gt;&lt;/QueryList&gt;</Subscription>
</EventTrigger>
</Triggers>
<Principals>
<Principal id="Author">
<UserId>S-1-5-18</UserId>
<RunLevel>LeastPrivilege</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>true</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>
<Priority>7</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
<Arguments>-Command "Get-NetAdapter | Enable-NetAdapter"</Arguments>
</Exec>
</Actions>
</Task>

Note that these depend on the correct audit logging being enabled on the machine in question, otherwise these tasks won’t trigger:

It also depends on how you use your PAW. If you regularly log out rather than shut down, you will need to add additional triggers to the tasks to handle the log off/log on events.

Import these tasks into Task Scheduler and use them at your own peril. You may run into issues if you don’t store any cached logons and simultaneously require a domain controller to be accessible at logon.

Quickly uninstall an MSI on multiple computers using WMI

Today I was working on reducing our vulnerability attack surface, and needed to remove Adobe Reader from our servers. It appears that it was installed as part of a VM image, but never maintained afterwards.

Long story short, rather than mess around with ConfigMgr baselines or Applications, I decided to go the direct route. To top it off, PowerShell remoting’s currently playing up. I ended up using WMI via the method that I outlined in my previous post.

Given an array of server names in $servers:

$servers | %{Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList 'MsiExec.exe /x "{AC76BA86-7AD7-1033-7B44-AA1000000001}" /norestart /qn' -ComputerName $_}

Trigger a remote GPUpdate without PSRemoting or PSExec

I recently enabled Windows Firewall on an unused server via GPO, but forgot to include the inbound RDP exception. This, of course, kicked me off my RDP session.

Rather than wait ~90 minutes for my revised GPO to take effect, I found that I could trigger a GPUpdate remotely using WMI (WinRM wasn’t enabled, and I didn’t want to use PSExec)

The following command does the trick:

Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList "gpupdate.exe" -ComputerName <computername>

View the creation date for AD-integrated DNS records

6 months in to my new job, and I’ve still got a big mess of old static DNS records to clean up from our Active Directory-integrated DNS.

The DNS management console doesn’t show any sort of date information, but I knew that because the data is stored in AD, there should be some sort of created/modified date on each record.

I had a look using ADSIEdit, and sure enough, there were the dates! Here’s a quick one-liner to pull out the records and their created/modified dates:

Get-ChildItem "AD:DC=contoso.com,CN=MicrosoftDNS,CN=System,DC=contoso,DC=com" | Get-ADObject -Properties Created,Modified | Select-Object Name,Created,Modified | Sort-Object -Property Created

Armed with the creation date of each record, I’m in a better position to determine which ones are no longer needed.

PowerShell: Get all unlinked GPOs

I decided to spend some of the quiet time now at the beginning of the new year to clean up Group Policy here at my new job. We had roughly the same number of GPOs as staff!

I’d already removed at least 10-20 GPOs manually, but I wanted a quick way to find all of the un-linked GPOs that were still just sitting around.

I found this great post by Mark Schill from back in 2013 that still does the job.

Mark’s solution, however, pulls out only the display name property. I needed to modify his solution a little, as I wanted to pipe the results to the Remove-GPO cmdlet. Here’s what I did:

Get-GPO -All | Where-Object { $_ | Get-GPOReport -ReportType XML | Select-String -NotMatch "<LinksTo>" }

The above one-liner gets all unlinked GPOs, and returns Microsoft.GroupPolicy.Gpo objects.

It goes without saying that you should be very careful when bulk-deleting anything. I first backed up all of my GPOs and dumped the list into text format to review it. I manually checked the settings on a few of the GPOs in question, and only then, deleted them.

Here’s how I generated the list to review:

$unlinkedGPOs = Get-GPO -All | Where-Object { $_ | Get-GPOReport -ReportType XML | Select-String -NotMatch "<LinksTo>" }
$unlinkedGPOs | Sort-Object -Property DisplayName | Select-Object DisplayName,CreationTime,ModificationTime | Format-Table

Correct Horse Battery Staple for PowerShell, AKA Random Memorable Password Generator

I’m a fan of using correcthorsebatterystaple.net for generating temporary passwords for users, rather than always using a static password. The site itself is a reference to the XKCD webcomic, and yes, I’m aware that there are plenty of opinions on the web about this topic.

password_strength

I’ve had the idea in the back of my mind for a while to see if I could replicate the site’s functionality in PowerShell. I noticed that the source code for the site is on GitHub, so I ducked over there to check out the word list.

I found that it’s possible to replicate most of the functionality of the site with just two lines of PowerShell (although it doesn’t result in very readable code):

  1. I used Invoke-WebRequest to grab the word list from GitHub
  2. I then expanded out the Content property, and split it up given the comma delimiter
  3. I then used a combination of the Range operator, Foreach-Object, [string]::join,Get-Random and the TextInfo class to generate a given number of passwords along these rules:
    1. 4 random words, each with the first letter capitalised
    2. A separator in between
    3. A random number between 1 and 99 at the end

Note that this isn’t failure-proof, and isn’t intended to be used in any complex scenario. There’s no error handling, and not much flexibility built in. It’s just a quick function you could put into your PowerShell profile.

You can, at least, do the following:
A PowerShell window displaying the output of Get-RandomPassword. Also shows the function being called with a Count parameter as well as a separator parameter

Here’s the code:

function Get-RandomPassword {
[OutputType([string])]
Param
(
[int]
$Count = 1,
[string]
$Separator = ''
)
$words = (Invoke-WebRequest 'https://bitbucket.org/jvdl/correcthorsebatterystaple/raw/773dbccc9b9e1320f076c432d600f19785c41792/data/wordlist.txt' | Select-Object ExpandProperty Content).Split(',')
1..$Count | ForEach-Object {"$([string]::Join($Separator,(1..4 | ForEach-Object {[cultureinfo]::CurrentCulture.TextInfo.ToTitleCase(($words | Get-Random))})))$Separator$(1..99 | Get-Random)"}
}

Script to create new local admin account for use with LAPS

I’ve got a bunch of older SOE machines that still had the local Administrator account enabled. As part of implementing Microsoft LAPS, I wanted to disable that account, and use a newly-created ‘LocalAdmin’ account with LAPS.

The account is created with a randomly-generated GUID as the password. The account’s password is going to come under the management of LAPS anyway. Additionally, it would be a terrible idea to hard-code a password into a script that’s stored in Sysvol.

If an account with that name already exists, the script will quit. Some basic events are also logged to the Event Log to indicate what happened.

My first revision of the script used ADSI to create the account and add it to the Administrators group, but I found that my mileage varied with that method. Some computers had the account created, but it wasn’t a member of Administrators.

It’s now set up to use plain “NET USER” and “NET LOCALGROUP” commands. This is an example of what would be executed:
2016-04-04 16_01_06

This script is designed to be set up as a computer Startup Script:

# The name of the account
$accountName = 'LocalAdmin'
$accountFullName = 'Local Administrator'
$accountComment = 'Backup Local Administrator Account'
# Any users listed here will be disabled by this script
$usersToDisable = 'Administrator','Guest'
# Set up some Event Log stuff
$sourceName = "$($MyInvocation.MyCommand.Name).ps1"
New-EventLog LogName Application Source "$sourceName" ErrorAction SilentlyContinue WarningAction SilentlyContinue
# If the account already exists, exit
if ((Get-WmiObject Win32_UserAccount filter "domain = '$Env:COMPUTERNAME' and Name = '$accountName'") -ne $null) {
Write-EventLog LogName Application Source $sourceName EntryType Information EventId 1 Message "$accountName already exists" ErrorAction SilentlyContinue WarningAction SilentlyContinue
exit
}
# Create the account
cmd.exe /c "net user $accountName `"$([guid]::NewGuid().guid)`" /add /y /comment:`"$accountComment`" /fullname:`"$accountFullName`""
# Add the account to the Administrators group
cmd.exe /c "net localgroup Administrators $accountName /add"
# Disable the specified users
$usersToDisable | Foreach-Object {cmd.exe /c "net user $_ /active:no"}
# Try and write an event to the Event Log
Write-EventLog LogName Application Source $sourceName EntryType Information EventId 2 Message "Created local administrator account: $accountName" ErrorAction SilentlyContinue WarningAction SilentlyContinue

Configure Desired State Configuration (DSC) on CentOS 7

Here’s a quick guide on how to set up DSC on CentOS 7. This requires OMI and DSC for Linux. It’s a bit of a pain to track down the downloads for these, and the OMI one doesn’t play ball when using wget, so I’ve put them on Dropbox for ease of use:

I always use the Minimal installation of CentOS. I started with CentOS-7-x86_64-Minimal-1511.iso

  1. Install CentOS, configure an IP address
  2. Connect in via SSH, or the console, run the following commands:
    1. yum install wget -y
    2. cd /home
    3. wget http://bit.ly/omi1084
    4. wget http://bit.ly/dsc_linux_111
    5. tar -zxvf omi1084
    6. tar -zxvf dsc_linux_111
    7. rpm -ivh omi-1.0.8.ssl_100.x64.rpm
    8. systemctl start omid.service
    9. rpm -ivh dsc-1.1.1-70.ssl_100.x64.rpm

To test connectivity from a Windows machine, do the following:

$session = New-CimSession -Credential (Get-Credential) -Authentication Basic -ComputerName <name or IP here> -SessionOption (New-CimSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck -UseSsl)
Get-CimInstance -CimSession $session -ClassName OMI_Identify -Namespace root/omi

You’ll be prompted to enter credentials after that first line. Then, after running Get-CimInstance you should see an output as per below:

2016-02-28 12_36_47

The CentOS machine is now ready to receive configs using DSC. I’ll be blogging more related stuff soon, but there’s a great write-up on the PowerShell Documentation pages that goes into more detail:

Get started with Desired State Configuration (DSC) for Linux