Things I actually like

Because I hear that I’m normally dissatisfied with schlocky half-assed semi-passionate stuff, I decided to make a list of things I actually, really do like. These are in no particular order.

  • My wife and kids (okay, and their spouses too)
  • Good food
  • Good beer and wine
  • More good beer and wine
  • Windows 10
  • Good Coffee
  • System Center Configuration Manager
  • Cute baby animals
  • Water. Because it’s hard to make beer and coffee without it
  • Movies
  • News about stupid people being eaten by beasts they were taunting
  • 24 hours without hearing any mention of politics
  • Good BBQ. Really really good BBQ
  • Podcasts
  • Sunny warm weather
  • Flowers
  • The Home Depot
  • Cool techy stuff
  • Tesla
  • Kayaking
  • Hiking
  • Drawing (when I’m in the mood)
  • Playing drums
  • Telling the same story over and over until it induces vomiting in others
  • Trying to figure out custom vehicle license plates
  • Flip flops
  • Ear buds (can’t travel without them!)
  • Frank Zappa (and Dweezil) (okay, and most of the people who have ever passed through the band at one point)
  • A nice smartphone
  • More coffee or beer
  • Hamburgers
  • Tacos
  • Indian food
  • Cholulah Sauce
  • Thai food
  • Bangladeshi food
  • Glennfiddich
  • People who read this far into my silly lists
  • Twitter!
  • Netflix
  • Wegman’s grocery
  • Fry’s
  • Dove Micelar bath soap
  • In-n-Out
  • Mountains
  • That incredible air freshener stuff they spray in most Hilton lobbies
  • Beaches
  • Cops pulling idiots over after they just flipped me off
  • Helping homeless people who don’t ask for it
  • Laughter (gut-busting, face down, smack the table kind)
  • Telling everyone who hasn’t seen Avengers Endgame yet how Thanos turns out to be Samuel L. Jackson at the very end
  • More coffee

No Answers Yet. Just Questions

So, I’m running into a technical snag and not having much luck solving it, yet. Hence, why I’m posting this here: in the hopes you might see this and respond with an answer. So far, the best (hopeful) solution offered is to run the process as a service (thanks Tony!), which I have not yet tried.

The Challenge

I need to schedule a PowerShell script (shown below) to run daily, using either a service account, or the local ‘SYSTEM’ account (I’ve tried both). This script needs to invoke functions from the PowerShell module ‘dbatools’, and produce output to a log file. Keep in mind that this is a ‘test’ script, and the actual script is far more eye-destructive ugliness than I can share here.

The platforms I’ve tested on (two so far) are the same:

  • Windows Server 2016 (domain-joined member server, entirely different forests/domains)
  • SQL Server 2017
  • PowerShell 5.1 (standard, built-in)
  • dbatools version 0.9.818

The Testing

Here is the script (below). When I run it in an interactive PowerShell console, it works fine. Even when I launch the console using “psexec.exe -i -s powershell -NoProfile” it works absolutely fine.

param (
[string] $server = $env:COMPUTERNAME,
[string] $database = "MyDatabase",
[string] $tablename = "table123"
Start-Transcript -OutputDirectory "x:\scripts"
try {
Write-Output "importing module: dbatools"
Import-Module dbatools
Write-Output "version: $((Get-Module dbatools).Version -join '.')"
Write-Output "verifying table"
if (Get-DbaDbTable -SqlInstance $server -Database $database -Table $tablename -ErrorAction Stop) {
Write-Output "found it!"
else {
Write-Output "not found"
catch {
Write-Output "error: $($Error[0].Exception.Message)"
finally {

The Scheduled Task

The Scheduled Task is set to run with the following configuration:

NamePS Test
TriggerDaily at 10:00 am
Actionpowershell -ExecutionPolicy ByPass -NoProfile -File x:\scripts\dbtest.ps1
RunAsNT Authority\System (aka “SYSTEM”)
OtherHighest privileges: enabled (checked)

The Problem

So far, no matter how I configure the task, it skips dbatools entirely and ends. Run it in an interactive console and it works perfectly. Here’s an example transcript from an interactive console invocation:

 Windows PowerShell transcript start
Start time: 20190430105649
Machine: CM01 (Microsoft Windows NT 10.0.14393.0)
Host Application: powershell.exe -NoProfile
Process ID: 7300
PSVersion: 5.1.14393.2248
PSEdition: Desktop
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.14393.2248
BuildVersion: 10.0.14393.2248
CLRVersion: 4.0.30319.42000
WSManStackVersion: 3.0
PSRemotingProtocolVersion: 2.3
Transcript started, output file is x:\scripts\PowerShell_transcript.CM01.ycN6+GrS.20190430105649.txt
importing module: dbatools
version: 0.9.818
verifying table
found it!
Windows PowerShell transcript end
End time: 20190430105649

And an example from running via a Scheduled Task as local ‘SYSTEM’ account…

 Windows PowerShell transcript start
Start time: 20190430103137
Machine: CM01 (Microsoft Windows NT 10.0.14393.0)
Host Application: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.EXE -ExecutionPolicy ByPass -NoProfile -File x:\SCRIPTS\dbtest.ps1
Process ID: 8312
PSVersion: 5.1.14393.2248
PSEdition: Desktop
PSCompatibleVersions: 1.0, 2.0, 3.0, 4.0, 5.0, 5.1.14393.2248
BuildVersion: 10.0.14393.2248
CLRVersion: 4.0.30319.42000
WSManStackVersion: 3.0
PSRemotingProtocolVersion: 2.3
importing module: dbatools
Windows PowerShell transcript end
End time: 20190430103142

It just stops at the import-module step and skips over to the “finally{}” block. No error is returned via “catch{}” which seems odd to me.

UPDATE: 04/30/2019

I added the following line just above the Import-Module for dbatools…

Write-Output "importing module: dbatools" 
#Import-Module dbatools
#Write-Output "version: $((Get-Module dbatools).Version -join '.')"
Write-Output "verifying database"
if (Get-SqlDatabase -ServerInstance "." -Name $database -ErrorAction Stop) {
Write-Output "database found!"
else {
Write-Output "database not found"

Here’s the updated transcript output…

importing module: dbatools
verifying database
database found!
verifying table
WARNING: SQLPS or SqlServer was previously imported during this session. If you encounter weird issues with dbatools, please restart PowerShell, then import dbatools without loading SQLPS or SqlServer first.
WARNING: To disable this message, type: Set-DbatoolsConfig -Name Import.SqlpsCheck -Value $false -PassThru | Register-DbatoolsConfig
found it!

Very weird results (to me, anyway).

UPDATE: 04/30/19 NO. 2 (The Electric Boogaloo)

Apparently, PowerShell 5.1 transcript features are what I was relying on to determine output/results (success/fail), which isn’t reliable. So, modifying my code (per suggestion of Shawn Melton, Slack “Sqlcommunity” channel) to use Out-File instead, seems to work!

# bad kitty!
Start-Transcript -OutputFolder "x:\scripts"
Write-Output "this works sometimes"

# good kitty!
$logfile = (Join-Path $pwd "logfile.log")
"$(get-date -format "yyyy-MM-dd hh:mm:ss") try this!" | Out-File $lotfile -Append
humor, Uncategorized

Random thoughts


A short list of things I’ve learned in my fifty-five years on this planet, which has been sitting in my drafts bin for 2 years.

  1. Not everyone who wanders is lost.
  2. Assuming everyone who wanders is lost could mean that you’re lost.
  3. If you hate Coke, it does not automatically mean you love Pepsi.
  4. Newer is not automatically better.
  5. If there’s a salesperson involved, it’s because it needed selling.
  6. Work is never really eliminated.  It’s just moved around.
  7. You can’t truly appreciate something until you’ve worked for it.
  8. The more legs a creature has, the more love it has.  Except when it gets past 8 legs, then it’s scarry.
  9. Most people complain most often about things they know the least about.
  10. The best programming language hasn’t been invented yet.
  11. Every generation wants the next to think that they had all the fun.
  12. Every generation thinks they had to work harder than the next.
  13. If it doesn’t cut expenses, or increase revenue, it’s probably junk.
  14. Fixing bugs is not refactoring.
  15. Chances are good that a reboot will fix it.


Some Dimented Dimensions of ConfigMgr Data

So, over the past few weeks, like most of the rest of the incredible people on our team, I’ve been working multiple projects for different customers around SCCM and other things. My biggest struggle is not only keeping names and details aligned with whomever I’m speaking with online, but keeping each of their respective constraints and obstacles aligned as well.

Disclaimer: This article just might almost possibly kind of make sense, so please: read to the end, before you print it out, ball it up, and light it on fire. In all seriousness, this is just ONE way to approach one challenge. I’m sure you may have a better way.

In my case, for example, in customer environment “A”, the SCCM admins have full access to the SQL instance which underpins their site, while customer environment “B” has to rely on DBA generosity to get anything, unless it’s available from the SSRS reports, or SCCM Queries.

For customer A (the first one), my tried-and-true cheezy-wheezy SQL-via-PowerShell pipeline happy meal kit works great. This is basically where I drop a library of deep-fried .sql files with a small fry, sugary drink and kids toy all in a nice box, then shoot those through a dbatools PowerShell paintball gun to get a pipeline-capable data pump, which they can do things with (see stupid diagram number 1 below).

Stupid Diagram 1

For customer B (the latter), this happy meal doesn’t work because it’s not gluten-free and doesn’t have the vegan solar-powered kids toy in the box. So that requires a somewhat less robust approach: WQL. SQL (T-SQL actually) is much much faster to process (in general) than squeezing a data cow through a WMI provider wormhole, but you work with what you have.

Stupid Diagram 2

So, to pull the same logical dataset “join” with WQL that the SQL option allows, requires some really arcane backflips with two wooden legs and a pogo stick. Why? I don’t know, but it seems like they didn’t have lunch with the SQL team and instead got high on gasoline fumes or something.

Maybe I’m being short-sighted, but to me, WQL is the JRE of SQL. Actually, that’s not fair, because WQL is at least useful. I know you can make WQL do joins by smoking MOF files in a kiln, but I’m too lazy for that. But I digress.

Anyhow, why do I prefer SQL over WQL for PowerShell automation? Mainly because SQL is easier to do joins, and it provides better performance (CPU, memory, etc.) on average than the WMI provider. It also requires more of the data shaping to be done by PowerShell after the source data is returned, and PowerShell isn’t as fast at performing dataset operations as a relational database. The more shaping you can do within SQL the better.

PowerShell SQL Query – Example 1

#forest gump example...

$sys = Invoke-DbaQuery -SqlInstance "CM01" -Database "CM_P01" -Query "select distinct name0 from v_r_system"

#forest gump the ping pong player example, or using a native join...

$clients = Invoke-DbaQuery -SqlInstance "CM01" -Database "CM_P01" -Query "select distinct sys.name0, cs.model0 from v_r_system sys inner join v_gs_computer_system cs on sys.ResourceID = cs.ResourceID"
# results example...
name0 model0
----- ------
CM01 Virtual Machine
DC01 Virtual Machine
FS01 Virtual Machine
WEB01 Virtual Machine
WS01 Virtual Machine
WS02 Virtual Machine

The downside to this is that the PowerShell code can easily (and quickly) get bloated, and you end up with two languages in one file, which can become a pain to test and troubleshoot over time.

Better yet, separate the two so that the PowerShell script and SQL query statements are in separate files. The SQL queries can be easily opened and tested in SQL Server Management Studio, making things much less painful.

PowerShell SQL Query – Example 2

First, the super-bloated SQL query file example…

dbo.v_R_System.Name0 AS ComputerName,
dbo.v_R_System.AD_Site_Name0 AS ADSiteName,
dbo.v_R_System.Client_Version0 AS ClientVersion,
dbo.v_GS_LOGICAL_DISK.DeviceID0 AS DiskID,
(dbo.v_GS_LOGICAL_DISK.Size0 / 1024.0) AS DiskSize,
(dbo.v_GS_LOGICAL_DISK.FreeSpace0 / 1024.0) AS DiskFree,
ROUND( (dbo.v_GS_LOGICAL_DISK.FreeSpace0 * 1.0 / dbo.v_GS_LOGICAL_DISK.Size0), 2) AS DiskUsed,
dbo.v_GS_PROCESSOR.Name0 AS Processor,
dbo.v_GS_X86_PC_MEMORY.TotalPhysicalMemory0 as Memory,
dbo.v_GS_OPERATING_SYSTEM.BuildNumber0 as OSBuild,
dbo.v_GS_SYSTEM_ENCLOSURE.SerialNumber0 as SerialNum,
dbo.v_R_System INNER JOIN
dbo.vWorkstationStatus ON
dbo.v_R_System.ResourceID = dbo.vWorkstationStatus.ResourceID LEFT OUTER JOIN
dbo.v_R_System.ResourceID = dbo.v_GS_PROCESSOR.ResourceID LEFT OUTER JOIN
dbo.v_R_System.ResourceID = dbo.v_GS_LOGICAL_DISK.ResourceID LEFT OUTER JOIN
dbo.v_R_System.ResourceID = dbo.v_GS_OPERATING_SYSTEM.ResourceID LEFT OUTER JOIN
dbo.v_R_System.ResourceID = dbo.v_GS_X86_PC_MEMORY.ResourceID LEFT OUTER JOIN
dbo.v_R_System.ResourceID = dbo.v_GS_SYSTEM_ENCLOSURE.ResourceID
(dbo.v_GS_LOGICAL_DISK.DeviceID0 = N'C:')
ORDER BY ComputerName

I told you it was bloated. Almost dead-fish-in-a-hot-summer-dumpster bloated. So, I save this carcass into a file named “cm_clients.sql”. This example was actually created in SSMS and saved, but you could use Visual Studio or whatever you prefer to SQL development and testing.

Now let’s hook up the electrodes to the PowerShell beast and get moving…

$rows = Invoke-DbaQuery -SqlInstance "CM01" -Database "CM_P01" -File "c:\queries\cm_clients.sql"

That’s it. One line of PowerShell, while the SQL logic is hiding in separate files. The output looks something like this…

Sidenote: I blogged about this already, but you can populate a folder with a library of .sql files, and use (get-childitem) and pipe it to Out-GrideView to make a handy and cheap GUI menu selector to impress your non-scripting coworkers. Something like this…

$QPath = "\\servername\sharename\folder"
$qfiles = Get-ChildItem -Path $QPath -Filter "*.sql" | Sort-Object Name
if ($qfiles.count -lt 1) {
Write-Warning "$qpath contains no .sql files"
$qfile = $qfiles | Select -ExpandProperty Name | Out-GridView -Title "Select Query to Run" -OutputMode Single
if ($qfile) {
$filepath = Join-Path $QPath $qfile
Invoke-DbaQuery ... you get the idea...

PowerShell WMI Query – Example 3

function Get-CmObjectCollection {
param (
[parameter(Mandatory=$True, HelpMessage="SMS Provider Name")]
[string] $Computer,
[parameter(Mandatory=$True, HelpMessage="Site Code")]
[string] $SiteCode,
[parameter(Mandatory=$True, HelpMessage="WMI Class Name")]
[string] $ClassName,
[parameter(Mandatory=$False, HelpMessage="Credentials")]
[System.Management.Automation.PSCredential] $Credential
$Namespace = "ROOT\SMS\site_$SiteCode"
try {
if ($Credential) {
$result = @(Get-WmiObject -Class $ClassName -ComputerName $Computer -Namespace $Namespace -Credential $Credential -ErrorAction SilentlyContinue)
else {
$result = @(Get-WmiObject -Class $ClassName -ComputerName $Computer -Namespace $Namespace -ErrorAction SilentlyContinue)
return $result | Select-Object * -ExcludeProperty PSComputerName, Scope, Path, Options, ClassPath,
Properties, SystemProperties, Qualifiers, Site, Container, __GENUS, __CLASS, __SUPERCLASS,
catch {
Write-Error $Error[0].Exception.Message

Without trying to shoehorn the entire set of SQL joins above into a WQL variation, which would look horrific without a ton of refactoring on Adderal and an ice bath, you can see with just two (2) datasets how this doesn’t scale very well…

$sys = Get-CmObjectCollection -Computer "CM01" -SiteCode "P01" -ClassName "SMS_R_System"
$cs = Get-CmObjectCollection -Computer "CM01" -SiteCode "P01" -ClassName "SMS_G_System_Computer_System"
$sys | % {
$resID = $_.ResourceID
$name = $_.Name
$model = $cs | ? {$_.ResourceID -eq $resID} | Select -ExpandProperty Model
$props = [ordered]@{
Name = $name
Model = $model
New-Object PSObject -Property $props

I suppose with enough brain juice, you could separate the query and processing logic with the WQL approach as well, but I’ll let you do that if you want.

Regardless of which approach you choose (or have chosen for you), the main thing to focus on is the data. With PowerShell you get the capability of pulling objects, which means you can filter and sort and do almost anything to shape the data you want to do what you need.


If you have the option, always use SQL. If you don’t, WQL can work, it’s just not as fun. Kind of like comparing Motley Crue with Lawrence Welk.

Brain is fried. Kirk out. Bed time.


The User Desktop Experience Support Expert’s Guide to Expert Support Guidance and Support for User Desktop Experience (or UDESEGESGSUDE for short)


There are many great blog posts, articles, whitepapers, videos and podcasts, that cover the plethora of angles involved with customizing the desktop “user experience” for users of Microsoft Windows 10 and Office 365 ProPlus. While they are often well-written and detailed, they often miss one fundamental question of “can” and “should”. This is what this blog post is about. It’s been a year since I used “plethora” in a sentence, so I’m celebrating with this blog post.

The Windows 10 Start Menu

Challenge: Customer/User/whiner insists they “need” to customize the Start Menu and/or Taskbar, to put application and web shortcuts directly in their faces. They cannot find and pin their own shortcuts. It is just completely impossible.

Solution: No. Tell them to pin their own damned shortcuts. You’ve got more important things to do.

User Profile Data

Challenge: Customer/User/Glue-sniffer wants to back-up and restore user profile data whenever a device is re-imaged, or replaced.

Solution: No they don’t. Cloud drives for everyone. Teach them how to back up their own stuff. If they sign your paycheck, then give them some help, anyone else: teach them.

Device Naming

Challenge: Like, OMG, your technicians are totally dependent on naming devices after the assigned users. Otherwise, it is “impossible” to identify their precious cat-hair covered laptop when they call for help.

Solution: Automatically name using serial numbers, and go find something more productive to do with your time.

Drivers and Imaging woes

Challenge: You spend countless hours finding, downloading, pruning, preparing, basting, seasoning, rubbing, basting, and package drivers for new models.

Solutions: (plural) [1] Use some automation tools like Driver Automation Tool (free plug), and [2] lean on your lazy-ass vendor reps to bring you the latest stuff. [3] Use AutoPilot, if possible, and stop reinventing the OS wheel everytime a new already-imaged device comes in the door.

Java Runtime

Challenge: You knew this was going to show up somewhere on my list, right? So, technicians are constantly dealing with multiple versions, and configurations, of the wonderful JRE for the 1990’s applications that users insist are critical to the organization.

Solutions: JRE sucks. Using JRE sucks. People talking about JRE sucks. People talking about using JRE sucks. Everything with JRE in it sucks. I hate JRE. Die die die!

Configuration Management

Challenge: You have 24 models to support, and new ones coming in all the time. Users have local administrator rights, and install an insane variety of products and versions. You don’t even know what you own.

Solution: One person with purchase authority and a (very) short list of supported models. Done. Move on. (Admin rights is next)

Local Admin Rights

Challenge: Users insist they need local admin rights, or they can’t do anything at work besides sit around complaining about not having local admin rights.

Solution: Give them some virtual machines and let them play. But the base OS is off-limits. It might help to state this officially as, “The base OS is off-limits, bitch.”

Password Maintenance

Challenge: Users keep forgetting their passwords. You have to maintain a scheduled process with a script, which sends email reminders to users about passwords about to expire.

Solution: Implement password self-reset. Turn off the scheduled 1990’s job. Then, go back to surfing Twitter and Facebook.

Software Packaging

Challenge: You find it more difficult to keep up with packaging new versions of software to maintain your environment.

Solution: Stop repackaging everything just because you’ve always repackaged everything. Sometimes a basic install is just fine. If it’s a pain in the ass installer, pick up the phone and rip a new hole in the vendor’s ass, to provide a better install experience, or you’ll “find a new piece of shit product to replace your piece of shit!!” Excessive caffeine prior to making that call, will often help.

Option 2 – Use the carrot approach: Explain to the vendor that by making their product easier to deploy and maintain, it will not only save your company time and money, but you may even give them a glowing endorsement of their cancer-curing world-peace-creating product. (you’d be surprised how often that actually works, btw.)

In-House App Dev

Challenge: You have a team of “developers” (glue-sniffing monkeys beating on keyboards) who make applications and dump them on your team to deploy. Only they’re not fully “packaged” for silent deployment.

Solution: Gluten-free Baseball bats.


Challenge: Users keep clicking on links in strange emails promising penis extensions, wads of free cash, and photos of naked entertainers. Virus infections are on the rise.

Solution: Implement something like ATA/ATP to mitigate the unwanted spam. Educate users on how to spot, and avoid, obvious links like “Gooogle.co”, “Click here to extend your penis, even if you don’t have one!” or “You won’t believe what Betty White’s vagina looks like now!” Not to mention, comparing the text links vs. the popups with the actual URL.


Basically, your job as an IT professional, is to work towards having as little work to do as possible. Automate everything. Shift the user-decision-making to the users. Be like the gas station attendant, who surfs Facebook all day whilst customers pump their own gas and buy lottery tickets from the vending machine.

If you can’t tell by now, I have strong belief in teaching users to deal with problems that they should be dealing with, and let IT worry about the complicated stuff. Backing up .mp3 and .docx files, is not complicated enough to assign highly-paid engineer to spend hours/days/weeks babysitting.

And, if you “sell it” properly to the users, it’s a win-win, because they see it as being trained and enabled. Most people like to feel enabled. So enable f*** out of them, and go focus on cool automation stuff. Every single time I’ve seen this put in motion correctly, it has paid off for everyone involved. Users are happier (and more capable), IT has less stupid crap on their plate, and more work gets done. Happy – happy – happy – happy – payday!


Bloggy stuff, part 47

This week, I have accomplished a few things which may or may not be significant. Who knows.

  1. I turned 55
  2. I got a bottle of Glenn Fiddich 12-year old Scotch
  3. I got to shoot the same shotgun John Wick used in John Wick and I hit the paper target at 22 yards every time. It was awesome.
  4. I updated CMHealthCheck to 1.0.8
  5. I uploaded a video of me attempting to code with PowerShell after a few beers. It’s almost entertaining
  6. I built a “farm table” for one of my daughters. It’s stained and drying in my garage.
  7. I taught my 100lb goof-ball dog to catch squeaky alligator toys in the air
  8. I discovered that I really like whiskey sour drinks
  9. I agreed that when the farm table is done, and moved into my daughter’s dining room, we are going to “break it in” with a dinner and shots of our preferred beverage. For her: Tequila. For me: Scotch
  10. I’m flying out to Houston on Monday. Then back home, then to Chicago in early April, then home, then to New York in June, and very likely somewhere else in between.


Great Moments in Obscure History: 1990

On this date, in 1990, a young man named Dave was working for an engineering firm in a small brick building buried somewhere in the southern United States.  His team was a merry group, who passed the time with an assortment of tasteless politically-incorrect humor and pranks.  It was on this date that one team member, Kevin, chose a random meme for the evening’s humor pool:  Convert real movie titles into porn movie titles.  Titles were selected at random for each person.

Mine was “Fist of Fear, Touch of Death” starring Bruce Lee.

I immediately responded with “Touch of Fear, Fisted to Death“.  Upon speaking these words out loud, I laughed so hard that I pulled a muscle in my left rib cage and had trouble breathing for a full day.

And that was Great Moments in Obscure History.