RMM Check: Unifi Health

At Slingshot, we’ve moved our Managed clients over to Unifi networking systems, and I wanted to use our RMM to directly monitor the Unifi Controller. The API isn’t officially documented, but I did find other resources: the community’s API documentation, CyberDrain’s examples, and the Unifi API browser. Add tons of other research and trial/error, and I finally accomplished a Powershell RMM script that monitors Device connections, resources/ports, and Alerts, and reports on a ton more.

Enough with the intros. Here’s an instance currently alerting in Solarwinds RMM:

…and More Info gives the full report (looks better in console — I wish SWRMM preserved whitespace):

Before I forget, some quick notes:

  • Needs a local account (limited admin/readonly) on controller
  • For UDMPs with firmware 1.6 or greater, use port 443; For older controllers, use port 8443
  • It defaults the Controller IP to the detected Gateway IP (we do a lot of UDMPs).
  • Several of the Device Status codes are documented nowhere, so this script might have the only public record of them (for posterity, I’ve figured out 2=pending adoption, 9=inform error, and 11=isolated).

And finally the Powershell — (self-consciously) still in progress with plenty of debug stubs, BUT with lots of useful production miles already under its belt:

<#	checkHealth-UnifiController.ps1
Purpose: for all sites on a controller, checks device connections, resources, ports, alerts
Author: 	Slingshot Solutions, www.slingfive.com
Params:		$IP (or hostname) of controller (default: detected gateway), [int]$PortNum (default:443), Username, PASSWORD
* needs a local account (limited admin/readonly) on controller
* For UDMPs with firmware 1.6 or greater, use port 443;  For older controllers, use port 8443

  $IP = (Get-wmiObject Win32_networkAdapterConfiguration |where-object{$_.IPEnabled -and $_.DefaultIPGateway}).DefaultIPGateway[0],
  $PortNum = 443, # or 8443 depending on controller version
# tweaks:
trap { $_; exit 1 }	# force RMM to treat unhandled runtime errors as ERROR!
#widen host buffer for better output on old PS versions:
$newsize=$pswindow.BufferSize; if($newsize.width -lt 100){$newsize.width=100}; $pswindow.BufferSize=$newsize
$newsize=$pswindow.WindowSize; if($newsize.width -lt 100){$newsize.width=100;$newsize.height=20}; $pswindow.WindowSize=$newsize
function short([string]$str,[int]$size){if($str.length -gt $size){return ($str.substring(0,$size-3)+"...")}else{ return $str}}
# vars:
$exitcode = 0
$report = ""
$FullRpt = ""
$StopWatch = [system.diagnostics.stopwatch]::startNew()

	write-host "PARAMETERS:"
	foreach ( $key in ((Get-Command -Name $MyInvocation.InvocationName).Parameters).Keys ) {
		$val = ((Get-Variable $key -ea SilentlyContinue).Value) -join ", "
		Write-Host "  $($key): $val" 
	write-error "Hey dummy, set your params!"
	exit 1000

$Credential = @{  username="$Username"; password="$Password"; remember=$True; strict=$True; } |ConvertTo-Json
if($PortNum -eq 443){ # UnifiOS 1.6 or newer:
	$BaseURI = "https://$($IP):$($PortNum)/proxy/network"
	$LoginURI = "https://$($IP):$($PortNum)/api/auth/login"
}elseif($PortNum -eq 8443){ # EdgeOS 1.5 firmware or older:
  $BaseURI = "https://$($IP):$($PortNum)"
	$LoginURI = "https://$($IP):$($PortNum)/api/login"

[Net.ServicePointManager]::SecurityProtocol = "tls12, tls11, tls"
[Net.ServicePointManager]::ServerCertificateValidationCallback = { $True }
Try {
	$login = Invoke-RestMethod -uri $LoginURI -Method POST -Body $Credential -ContentType "application/json" -SessionVariable UniFiSession | out-null
}catch{	#wrong username/password = "The remote server returned an error: (400) Bad Request"
	write-error "* ERROR - Api Connection Error: $($_.Exception.Message)"    
	$exitcode = 1010
if($exitcode -eq 0){
	#write-host "CONNECTED`r`n"

if($exitcode -eq 0){
	try {
		$urlSites = "$($BaseURI)/api/stat/sites"
		$sites = Invoke-Restmethod -Uri $urlSites -Method GET -WebSession $UniFiSession
		#$report += "* ERROR - Sites Query Failed: $($_.Exception.Message)`r`n"
		write-error "* ERROR - Sites Query Failed: $($_.Exception.Message)`r`n"
		$exitcode = 1011

if($exitcode -eq 0){
	# DIRTY HACK: UDMPs lose their auth on the second call (Something ServerCertificateValidationCallback related), so just pound it back every time:
	Invoke-RestMethod -uri $LoginURI -Method POST -Body $Credential -ContentType "application/json" -SessionVariable UniFiSession | out-null

	$rptDevices = ""
	$rptAlarms = ""
	Foreach ($site in $sites.data){  # SITE
		try {
			$urlDevices = "$($BaseURI)/api/s/$($site.name)/stat/device/"
			$devices = Invoke-Restmethod -Uri $urlDevices -Method GET -ContentType "application/json" -Headers @{"Accept"="application/json"} -WebSession $UniFiSession
			write-error "* ERROR - Device Query Failed: $($_.Exception.Message)`r`n"
		$shortsitename = short $site.desc 7

		$FullRpt += "`r`nSITE '$($site.desc)' - $urlDevices :`r`n"

		$DeviceStateNames = @{0="disconnected"; 1="connected"; 2="pending adoption"; 3="3(?)"; 4="upgrading"; 5="provisioning"; 6="heartbeat missed"; 9="inform error"; 11="isolated" }
		$DeviceModelNames = @{"BZ2"="UniFi AP"; "BZ2LR"="UniFi AP-LR"; "U2HSR"="UniFi AP-Outdoor+"; "U2IW"="UniFi AP-In Wall"; "U2L48"="UniFi AP-LR"; "U2Lv2"="UniFi AP-LR v2"; "U2M"="UniFi AP-Mini"; "U2O"="UniFi AP-Outdoor"; "U2S48"="UniFi AP"; "U2Sv2"="UniFi AP v2"; "U5O"="UniFi AP-Outdoor 5G"; "U7E"="UniFi AP-AC"; "U7EDU"="UniFi AP-AC-EDU"; "U7Ev2"="UniFi AP-AC v2"; "U7HD"="UniFi AP-HD"; "U7SHD"="UniFi AP-SHD"; "U7NHD"="UniFi AP-nanoHD"; "UFLHD"="UniFi AP-Flex-HD"; "UHDIW"="UniFi AP-HD-In Wall"; "UCXG"="UniFi AP-XG"; "UXSDM"="UniFi AP-BaseStationXG"; "UCMSH"="UniFi AP-MeshXG"; "U7IW"="UniFi AP-AC-In Wall"; "U7IWP"="UniFi AP-AC-In Wall Pro"; "U7MP"="UniFi AP-AC-Mesh-Pro"; "U7LR"="UniFi AP-AC-LR"; "U7LT"="UniFi AP-AC-Lite"; "U7O"="UniFi AP-AC Outdoor"; "U7P"="UniFi AP-Pro"; "U7MSH"="UniFi AP-AC-Mesh"; "U7PG2"="UniFi AP-AC-Pro"; "p2N"="PicoStation M2"; "US48PRO"="UniFi Switch Pro 48"; "US8"="UniFi Switch 8"; "US8P60"="UniFi Switch 8 POE-60W"; "US8P150"="UniFi Switch 8 POE-150W"; "S28150"="UniFi Switch 8 AT-150W"; "USC8"="UniFi Switch 8"; "US16P150"="UniFi Switch 16 POE-150W"; "S216150"="UniFi Switch 16 AT-150W"; "US24"="UniFi Switch 24"; "US24P250"="UniFi Switch 24 POE-250W"; "US24PL2"="UniFi Switch 24 L2 POE"; "US24P500"="UniFi Switch 24 POE-500W"; "S224250"="UniFi Switch 24 AT-250W"; "S224500"="UniFi Switch 24 AT-500W"; "US48"="UniFi Switch 48"; "US48P500"="UniFi Switch 48 POE-500W"; "US48PL2"="UniFi Switch 48 L2 POE"; "US48P750"="UniFi Switch 48 POE-750W"; "S248500"="UniFi Switch 48 AT-500W"; "S248750"="UniFi Switch 48 AT-750W"; "US6XG150"="UniFi Switch 6XG POE-150W"; "USXG"="UniFi Switch 16XG"; "UGW3"="UniFi Security Gateway 3P"; "UGW4"="UniFi Security Gateway 4P"; "UGWHD4"="UniFi Security Gateway HD"; "UGWXG"="UniFi Security Gateway XG-8"; "UP4"="UniFi Phone-X"; "UP5"="UniFi Phone"; "UP5t"="UniFi Phone-Pro"; "UP7"="UniFi Phone-Executive"; "UP5c"="UniFi Phone"; "UP5tc"="UniFi Phone-Pro"; "UP7c"="UniFi Phone-Executive";
		"UDMPRO"="UniFi Dream Machine Pro"}

		Foreach ($device in ($devices.data)){
			$DeviceModelName = $DeviceModelNames[$($device.model)]
			$DeviceStateName = $DeviceStateNames[$device.state]

			if( $device.default -eq $True){	# PENDING ADOPTION:
				$vwireEnabled = ($device.vwireEnabled -eq $True)
				$discovered_via = $device.discovered_via	#scan or l2
				$FullRpt += "* $($device.type) $DeviceModelName, ip:$($device.ip), mac:$($device.mac), state:$DeviceStateName $($device.state), discovered_via:$discovered_via, wireless:$vwireEnabled`r`n"				
				$rptDevices += "* PENDING ADOPTION$(if($vwireEnabled){" (WIRELESS)"}): $shortsitename > ($DeviceModelName)`r`n"
				$exitcode = 1010

			}elseif ($device.adopted -eq $True){
				if($device.state -eq 0 -or $device.state -eq 9 -or $device.state -eq 11){	# DISCONNECTED, INFORM ERROR, ISOLATED:
					$FullRpt += "* '$($device.NAME)' - $($device.type) $DeviceModelName, ip:$($device.ip), mac:$($device.mac),`r`n    state:$($device.state) $DeviceStateName, adopted:$($device.adopted), disabled:$($device.disabled -eq $True)`r`n"				
					if($device.disabled -ne $True){	# don't care if it's disabled
						$rptDevices += "* $($DeviceStateName.toUpper()): $shortsitename > '$($device.name)'`r`n"
						$exitcode = 1011
				}else{	# OTHERWISE:					
					$uptime = new-TimeSpan -Seconds (0+ $device.'system-stats'.uptime)

					$FullRpt += "* '$($device.NAME)' - $($device.type) $DeviceModelName, ip:$($device.ip), mac:$($device.mac),`r`n    state:$($device.state) $DeviceStateName, uptime:$uptime, cpu:$([int]$device.'system-stats'.cpu)%, mem:$([int]$device.'system-stats'.mem)%, current:$(!$device.upgradable) (FW v$($device.version))`r`n"

					if( [math]::Round($device.'system-stats'.uptime) -lt "300") { 
						$rptDevices += "* $shortsitename > $($device.name) : Disconnected `r`n"
						$exitcode = 1010
					if( [math]::Round($device.'system-stats'.cpu) -gt "90.0") { 
						$rptDevices += "* $shortsitename > $($device.name) : CPU usage of $($device.'system-stats'.cpu)% `r`n"
						$exitcode = 1012
					if( [math]::Round($device.'system-stats'.mem) -gt "90.0") { 
						$rptDevices += "* $shortsitename > $($device.name) : Memory usage of $($device.'system-stats'.mem)% `r`n"
						$exitcode = 1013
					if( $device.upgradable -eq $true ) {
	#					$rptDevices += "* $shortsitename > $($device.name) : Firmware upgrade available <-- IGNORING til QA recovers`r`n"
	#					$exitcode = 1014

					#if($device.port_table.count -gt 0){	#don't need to see AP Pros' secondary ports
					if($device.type -eq 'usw'){
						$FullRpt += "  "+ ($device.port_table |ft port_idx,name,enable,up,is_uplink,port_poe,autoneg,speed,full_duplex,network_name -AutoSize |out-string).trim().replace("`n", "`n  ") +"`r`n"
					Foreach ($port in $device.port_table.data){
						if($port.stp_state -eq "discard"){
							$rptDevices += "* $shortsitename > $($device.desc) > PORT $($device.name) : blocked due to STP issues `r`n" 
							$exitcode = 1017


		Foreach ($device in $devices.data.wan1 |where-object {$_.name}){	#weirdly duplicates with blanks otherwise
			$FullRpt += "* FW WAN1 '$($device.name)' - is_uplink:$($device.is_uplink), up:$($device.up), ip:$($device.ip), netmask:$($device.netmask), gateway:$($device.gateway)  `r`n"
			if($device.is_uplink -and $device.up -ne $True) { 
				$rptDevices += "* $shortsitename > WAN1 $($device.name) : link down `r`n" 
				$exitcode = 1015
		Foreach ($device in $devices.data.wan2 |where-object {$_.name}){#weirdly duplicates with blanks otherwise
			$FullRpt += "* FW WAN2 '$($device.name)' - is_uplink:$($device.is_uplink), up:$($device.up), ip:$($device.ip), netmask:$($device.netmask), gateway:$($device.gateway)  `r`n"
			if($device.is_uplink -and $device.up -ne $True) { 
				$rptDevices += "* $shortsitename > WAN2 $($device.name) : link down`r`n" 
				$exitcode = 1016

		# DIRTY HACK: UDMPs lose their auth on the second call (Something ServerCertificateValidationCallback related), so just pound it back every time:
		Invoke-RestMethod -uri $LoginURI -Method POST -Body $Credential -ContentType "application/json" -SessionVariable UniFiSession | out-null

		try {
			$urlAlarms = "$($BaseURI)/api/s/$($site.name)/stat/alarm/"
			$alarms = Invoke-Restmethod -Uri $urlAlarms -Method GET -ContentType "application/json" -Headers @{"Accept"="application/json"} -WebSession $UniFiSession
			$report += "* ERROR - Alarm Query Failed: $($_.Exception.Message)"
			write-error "* ERROR - Alarm Query Failed: $($_.Exception.Message)"
		Foreach ($alarm in ($alarms.data)){
			if(! $($alarm.handled_time)) {	# ???
				$rptAlarms += "* ALERT: $shortsitename > '$($alarm.ap_name)' : $($alarm.msg) @$([datetime]$alarm.datetime -f "yyyy-MM-dd-Hmmss") `r`n" 
				$exitcode = 1018


	$report += "$rptDevices `r`n"
	$report += "$rptAlarms `r`n"

if($exitcode -gt 0){
	write-host "FAIL - problems found:"
	write-host "OK - no problems found!"

 write-host "CHECKED DATA:"
 write-host " "($FullRpt.replace("`n", "`n  ")).trim() #format

write-host "PARAMETERS:"
foreach ( $key in ((Get-Command -Name $MyInvocation.InvocationName).Parameters).Keys ) {
	$val = ((Get-Variable $key -ea SilentlyContinue).Value) -join ", "
	Write-Host "  $($key): $val" 
write-host "CONTEXT:"
write-host "  Script Path:" (get-item $MyInvocation.InvocationName)
write-host "  Script Last Updated:" (get-item $MyInvocation.InvocationName).LastWriteTime " (try -5hrs [SW saves as UTC])"
write-host "  Execution Time: Total $($elapsed_Total)sec"

write-host "exitcode: $exitcode" 
exit $exitcode

NOTE: updates since publishing this have so far added:
* proper detection of the “pending adoption” state
* awareness of the undocumented “adopting” state
* awareness of the undocumented UBB, USP and USW-Flex-Mini models
If anyone’s dying to see the latest version, let me know in the comments.

ExcelMicro.com RSS feeds

Are you an ExcelMicro partner who needs to stay in the loop on what they’re doing?  

They’ve got some great services and folks, but it sounds like they’re swamped with a lot of internal changes over the last couple years.  Unfortunately, that leaves them a bit weak on partner communication…

Their ONLY official way to not get surprised by changes is to manually/constantly check their support portal web pages.  After extended nagging for them to publish ANY info on Proofpoint releases, they did finally add that a few months ago.  But still no mailing lists, no RSS feeds, no carrier pigeons — just keep clicking refresh til something shows up…

So I took matters into my own hands: Using Feed43’s fantastic service, I wrote custom regular expressions to scrape EM’s content, which I’ve published into my own RSS feeds.  If you’re looking to stay in the ExcelMicro loop, feel free to subscribe to these feeds:

I expect EM will eventually get official feeds so these aren’t needed, but til then, enjoy!

Lift A Finger?

I’ve mentioned before that I have pet peeves around driving.  And I recently saw a bumper sticker that reminded me that I’m not alone:
“Forget About World Peace / Visualize Using Your Turn Signals”

Actually, I saw it again, but in a new way   At a glance it’s just snarky, and I’m all about snark.  But bad driving begets traffic jams, and time to reflect on the bumper stickers in front of you…

Everyone’s an armchair general of world politics, and these days everyone has a soapbox for that too (yay Facebook).  But I only see 1/3 of Cincinnati drivers use their indicators to signal their turns or lane changes.  (And I think 1/3 is generous).  It’s surely the easiest effort in driving.

Literally, only 1/3rd of the world will LIFT A FINGER for safer driving.

I found this realization depressing for a few days, but I’ve decided it should be a Jeremiad, or a harsh reminder to do better:
The little stuff matters.   Before asking everyone else for a better world, Lift A Finger to start making it happen.  



I mean catch-up.
Too much work for too long meant many accrued life complications, so I’m on a purge…


Breaking news: Identity theft is a bad thing

One target is paper: The last 3 weekends have included many hours of sorting through paper files for shred, toss, or reuse as scrap.  (Yes, I caught my mother’s frugality, so our family prints on the blank sides of old stuff, which is fun when you discover weird personal history on the flip side years later.)

In the shred category: I award special recognition to Anthem, Discover, and US Bank for taking an extra decade to notice any news about that new-fangled “identity theft”.  (It took them until ~2006 to start removing complete SSNs and account numbers from monthly statements)

The other is Digital: Today I turned to my blog feeds.  15 years had “collected” roughly 450 feed subscriptions in my reader, which I’m still reading daily.  …Well, I knew some of those 450 had gone dark, but managing them has been a recurring pain (browsing feeds by name isn’t easy in Feedly – how about some basic alphabetical sorting?).



So I decided to clean house on my feeds.  +A couple hours = a couple realizations:

  1. It’s messed up that I can get sentimental about an RSS Feed.  WebMonkey, I still miss your irreverent teaching methods, and I probably owe you my career.   But in culture of obscene plenty, I gotta start dropping some burdens.
  2. Blogs are Dead!  At least half of those feeds are now offline or haven’t been updated in 5-10 years.  Now I know why the huge feed count was only netting a few dozen posts per day.
    But why have bloggers given up?   Facebook and its ilk are obvious.  Long-form takes too long, and “Sharing” is now a grunt-button (yes, a grunt is how much value you’re sharing with those things) .   With grunt-buttons over at the HealthyViralConservatives page (that’s probably a thing), we smart people are all now stealing our own thunder.  But minimal effort = minimal value, so those with nothing to share are sharing the most, and discourse is dead.

…But wait, isn’t this Rob’s like, yearly blog post?  Yeah, I’m not done yet — that’s another comeback in the works.

Not Awesome

Not Awesome

But what direction?  Journaling is a great exercise and enjoyable, but what do I have that interests folks?
Comments would be a great measure, but I’ve never been popular enough for much of them, and folks have even LESS time for comments in the grunt-jab era, so they’re out. But …Akismet brags it’s outright blocked 18,279 spam comments just since I switched to WordPress 2 years ago.  And I get regular notices of spam comments quarantined for review (and that’s easy.  The last blog move was good to me, and I’d like to heartily thank the whole WordPress community.  Posting is way more likely when I’m not just cleaning up.)
So, without bothering on analytics, maybe the spammers know something.  They LOVE two ancient posts in particular:  my write-up on Royal TS (RIP), and another about line-breaks in VBScript.  (Was 2005 special?  It was arguably the golden age of blogging, but I’ve no idea how that could still echo now.)

So I’m gonna go with the topics: “meta-throbs” journally junk like this doesn’t enthrall, but every geek loves good Tools and Tips.  And I’ve always got tons of those, so I’ll try to get back to sharing them here soon.

I’d love requests too, but certainly won’t get my hopes up.  After all, there’s probably a heart-warming new LiberalCatVideo with a grunt-button.

Trans Cat

Secret to skip DHCP check in SBS2011 CTIW

I’ve sat on this far too long.  In 2013 I was setting up SBS 2011 (rebuilding it for a Slingshot client, actually), and hit the classic gotcha where the Connect To Internet Wizard insists on being the DHCP server.  My workaround was usually to temporarily disable the router’s DHCP, finish the wizard, then re-enable the router’s DHCP.

Unfortunately, this time I was working remotely after hours, and had no access to their router or its admin to do anything about it, so I was at risk of losing the night’s work and the client finding their stuff still down the next day.

Fortunately, I already had Microsoft support on the line for other matters.  They knew a workaround for this — when I asked about it, this is the registry screenshot I got:
secret to skip DHCP check in SBS2011 CTIW

For the record, that’s HKLM\SOFTWARE\Microsoft\SmallBusinessServer\Networking and a DWORD of SkipDHCPConfig = 1

I asked if this was public knowledge, and the answer was no.  I’ve saved this for a while, but with SBS 2011 now abandoned I think the knowledge should benefit others.  So I hope this helps someone else!   (I actually just needed that info myself, for a similar situation 🙂


How To Not Get Extorted By Ransomware

(Following is some material that Slingshot recently wrote up to help guide our customers through securing their IT systems against Ransomware.  It’s too good to not share with the world, though, so here ya go.  BTW, we pair this with a personalized recommendation — just holler if your organization would like a Slingshot consultation!)



CryptoWall, CryptoLocker, and a variety of other names – they’re all Ransomware, one of the newer and most dangerous types of malicious software (Malware) threatening anyone who uses computers.  Once a computer is infected, these threats can lock your users out of their computers or encrypt your data irreversibly, including data on your servers.

Once the computers and/or data are compromised, users are told they can pay a “ransom” to recover their system and data access, often for thousands of dollars.  Since the culprits are often overseas, they reach over and hide behind the Internet in ways that regular law enforcement can’t help you.  This is extortion, and you just don’t want to be there!

Instead, we want to help you stay safe, so we’ve written this document to do two things:

  • Outline your options to Avoid, Block, Catch, Limit and Recover From Ransomware.
  • Recommend your best options to quickly and thoroughly secure your organization.


Fortunately, there are plenty of ways to avoid paying your way out of Ransomware!

1) Avoid It with User Training:

Some technologies can reduce your risks, but the most important part of the puzzle is training users.  Besides Ransomware, training can help you avoid other threats, such as:

  • Phishing: Masquerading as a trustworthy entity to acquire sensitive information such as usernames, passwords, and credit card details, and sometimes money.
  • Whaling: Phishing that specifically targets organizations’ empowered decision makers.  Targeting details are harvested through Internet searches and/or social engineering attempts.  Hackers will sometimes register a very similar-looking domain, and then email decision makers from what appears to be their bosses, directing them to transfer money to offshore accounts.
  • Pharming: Malware is secretly planted in your computer to hijack your web browser.  When you type in the address of a legitimate Web site, you’re redirected to a fake copy of the site without realizing it, where the attackers can harvest your sensitive information.
  • Vishing: Phishing via telephone, this relies on social engineering techniques to trick you into providing sensitive information.

Informed users are the first line of defense against Internet security threats!  Slingshot can train your staff by several means: easy-to-read advisory documents, “lunch and learn” sessions with a hands-on visual component and Q&A, one-on-one training, or any combination of these.

2) Block It with Perimeter Defense:13604255104_b8cf62e8c0_m

The next defense is to block threats at a network level before they reach users (or vice versa).  Here are options and recommendations for each network threat:

  • Malicious File FilteringBlocks access to Malware and other known dangerous files.
    → We recommend Cyberoam firewalls paired with an Antivirus Filtering subscription for this.
  • Malicious Traffic Filtering – Blocks malicious traffic, like encryption keys used by Ransomware.
    → We recommend Cyberoam firewalls paired with an IPS subscription for this.
  • Geo BlockingBlocks all traffic from countries known for their criminal Internet reputation.
    → We recommend Cyberoam firewalls with Geo Blocking configured.
  • Web Filtering – Blocks access to websites classified as dangerous. Many firewalls can do this as a blanket solution, but most folks want something more sophisticated.
    → We recommend WebFilter (a Slingshot Managed Service), which we can also setup with per-user policies to also shield you from productivity and legal risks.
  • Email Filtering – Blocks spam, dangerous email content, and fraudulent emails. Some email servers can be setup to do some of this, but not very effectively.
    → We recommend MailControl (another Slingshot Managed Service), which we setup and keep tuned up, but which gives users easy self-management over their own filtering.

3) Catch It with Endpoint Protection2182760200_2825aac351_m

Should Ransomware or other malware still get past the user and firewall (e.g. by infected flash drive or using a work laptop outside the office network), additional protections are available:

  • Antivirus software – Installed software to detect and block malware. Windows 8+ has Defender built-in, which is free, but basic and unmanageable. → We recommend Slingshot’s Managed Antivirus (a Slingshot Managed Service) which uses the top-ranked BitDefender scanning engine, and which we setup, manage and monitor.
  • CryptoPreventA third party software application that installs on each computer and blocks many Ransomware infections. The commercial version costs around $100 for 50 licenses.
  • Malwarebytes Anti-RansomwareNew program (still in beta) that detects ransomware behavior and blocks the threads that are trying to encrypt files. Currently available for free.

4) Limit It with Secure Settings4250220079_b577b9b0c1_m

Even with a comprehensive protection plan in place, malware is constantly morphing to get around those measures.  If malware gets past the user, firewall, and malware detection, we want to limit its impact as much as possible.  There are two good ways to do this:

  • Software Restriction Policy (SRP) – A built-in Microsoft utility which can be configured to prevent executable files from running from directories that malware commonly tries to use. Basically, this keeps malware from having a foundation to work off.
  • Least User Privilege (LUA) – This is just a matter of using Standard User accounts for everyday work (rather than Administrator accounts). If a user account can only make changes to his areas, Ransomware running as that user has the same limitations.  (A best practice anyway, LUA also protects against staff accidentally taking down systems, as well as from snooping or malicious users tampering with things they shouldn’t.)
  • Network Share Security – Similar to LUA and “need to know” thinking, server network shares should have their access set to only allow the level of access that’s needed by each user or user group. This requires some thinking about an organization’s groups and who needs what.  (Also a best practice for the same reasons as LUA above).

5) Recover From It with BackupsStone-Rolled-Away1[1]

Finally, no matter what you do, bad things can still happen.  If worse comes to worst, there are options for data recovery without paying ransom.
→ Any properly configured server should follow the 3-2-1” Backup Rule:  3 backups, on 2 different media, and 1 offsite.

  • Volume Shadow Copy / Restore Previous Versions / System RestoreProperly configured Windows servers and workstations “snapshot” a backup of their files several times per day. In most cases, these previous versions can be easily restored over the encrypted versions.  Unfortunately, some malware knows how to also corrupt the previous file versions – LUA (above) helps prevent that.
  • Nightly Backups This is the second layer, usually a full-system (“bare metal”) Windows Backup to an external drive or NAS. Should Volume Shadow Copy have trouble, we can still restore files from this.
  • Offsite Backups – Finally, in the event of any catastrophe, you want a copy of your data offsite. We recommend CrashPlan or JungleDisk for this.


GPO To Set Firewall Exception For Windows 10 RDP

Slingshot recently rolled out several Windows 10 Pro systems for a customer, and discovered their existing GPO’s firewall rules weren’t enough to allow RDP from within the LAN.

Susan’s post Windows 10 and SBS/Essentials Platforms showed how to do it as a one-off.  But I wanted a GPO!  Google let me down, returning a lot of confusion and complicated workarounds.  (I shared this with Susan and she blogged it, which reminded me of my own blog, duh, so here it is!)

I went exploring GPO, and found the right setting under the Advanced Firewall section:  Computer Configuration->Windows Settings->Security Settings->Windows Firewall with Advanced Security->Inbound Rules->New Rule->Predefined->Remote Desktop – RemoteFX :
GPO for W10 RDP

That’s it!  Tested and confirmed working in production.

(Note: this is in addition to the usual rules at Computer Configuration->Administrative Templates->Network Connections->Windows Firewall->Domain Profile)


Upgrade HP Stream to Windows 10


Last December I grabbed a deal on a cheap HP Stream laptop for my family.  It’s been a nice little convenience screen, but with gotchas:
1) Tiny hard drive – 30GB total, with 10 gone to Windows (as expected).  Then HP took another 10 for their partition.
→ So just 10GB for you!  A few apps later and even OneDrive cannot save you.
2) Tiny RAM – 2GB, but only 0.7GB available idling.  HP complicated the 2GB also by bizarrely installing 64-bit Windows (against Microsoft’s recommendation) and wasting its limited RAM.
→ Multiple users?  “Please log out instead of switch user, honey”.
3)…and none of it is upgrade-able.
→ Well, you can do like I did and add a big fast SD card for more storage, but that’s about it.

TWindows 10 on a Streamhen Windows 10 came out, and it’s generally great.  It extended Win8’s unified Microsoft logins, and rolled in what was formerly Live’s Family Safety features, making it great for families.  And with the Start Menu back, I now have no worries about moving cheese for business users.  So to me, the Windows 10 upgrade is an automatic yes for any Windows 8 systems or new PCs.

Add 10 to the above challenges, and of course I wanted to kill seven in one blow!   Specifically, 1) move to Windows 10, 2) reclaim drive space from HP’s extra partition, and 3) reclaim RAM from HP’s dumb 64-bit choice.  (Alright, 3 in one blow, whatever).

That brings us to a month ago, when I started this blog post….
TL;DR: 2/3 ain’t bad.  Success on #1 for drive space.  No-go on RAM.  

Problem: HP provides NO 32-bit drivers for the Stream.  Result: non-working touchpad.
Many hours of reinstalling various editions of Windows, and every other trick I’ve learned over 20+ years, and I got Device Manager looking happy, but with no valid chipset drivers (I believe Intel’s Trusted Execution Engine Interface is the main culprit) to expose the touchpad device to Windows.  Actually, I saw a dramatic difference on RAM usage (about 25% more available), but no working touchpad (which is critical for a convenience device like the Stream).  I’ll leave that sad story there — if you want more, lemme know.

But there’s still the upgrade and the drive!

Problem #2: Not enough free-space to do the upgrade.
I ran into several snafus with this, but we can get around that!  Here’s how:

  1. Get an empty 16GB flash drive.
  2. Backup all your data to OneDrive.  Just do it now.  It’s built-in!  And now your stuff is backed up and can just self-load into any future Windows installs.
  3. Use Windows built-in “Reset this PC” feature to return your Stream to factory defaults.  This will wipe everything and free up a ton of space.
  4. Download and run MediaCreationTool64.exe, and let it download and check away.  (FYI, the 32-bit MediaCreationTool.exe will NOT work, and will just pop up an empty or useless error message…)
    When the tool still(!)  complains about not having enough space…
  5. Plug in an empty 16GB USB flash drive and point it at that for temporary storage.  This should let it run.  Be patient, it’s downloading an entire DVD and replacing your OS.  Maybe let it run overnight, but it should work.
  6. You now have Windows 10!
    Login with your Microsoft account and turn on OneDrive.  Your stuff will appear.
  7. …And HP’s partition disappeared!  This is a nice surprise, as I otherwise would have given about 20 more steps to capture product key, capture drivers, repartition the drive, and scratch-install Windows.
    I suspect it means “undoing” the upgrade probably won’t really put it back the way it was, but that way stunk, and this saves you tons of trouble.

Unfortunately, it’s still high RAM usage (I’m currently at 72% with a single Chrome tab open and nothing else running), but it’s significantly more free drive space and Windows 10.   Heck of an ordeal, but a worthwhile improvement.