Creating a headless DNS-based adblocker with PiHole on a Raspberry Pi Zero W

Required Hardware

  • Raspberry Pi Zero W ($10 –
  • Power adapter (5 volt, 2.5 amp) – I bought mine on EBay for $3.82
  • Optional: PI Zero W Case ($6 –
    MicroSD Card, I’m using a cheap class 10 8GB card, which should be plenty if this Pi will only be used for PiHole.

Required Software

  • Win32DiskImager – Use this to write our OS image to MicroSD
  • Putty – Use this to connect via SSH into Pi
  • Notepad++

Preparing the MicroSD

  1. Download the OS image, Raspbian Stretch Lite, from here:
  2. Extract the .IMG file from the ZIP to a convenient location.
  3. Prepare your SD card for writing. I like to clean the disk before proceeding to ensure old partitions and such are wiped out:
  4. Write the .IMG file to disk with Win32DiskImager or similar. Say “Yes” to warning prompt.
  5. When this finishes, you’ll probably get at least one error about unreadable file system. Don’t worry about it. Do not eject the MicroSD card yet. You should have a new drive listed as “Boot” now.

Enabling SSH and Provisioning Wireless Connectivity

One of the things I love about the Pi is how easy it is to turn it into a headless system. To do this, we need to specify our wireless network credentials ahead of time, as well as enable SSH.

SSH is now, by default, disabled in Raspbian Stretch Lite out of the box. Enabling it is as simple as literally creating a blank text file in the root of the boot partition named “ssh”. Your save menu in Notepad++ should look like this:

Similarly, to provision network connection, we’ll create a file in the same partition named “wpa_supplicant.conf” containing the following:

ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev

ssid="Network Name”

You’ll need to set EOL Conversion to Unix to make sure the file is parsed correctly. Something to do with line break formatting.

Your save menu should look like this:

Verify both wpa_supplicant.conf and SSH are present on your boot partition, then eject the MicroSD card and install in the Pi Zero W.

Moving to the Pi…

Power up the Pi Zero W, ensure you connect the MicroUSB cable to the port labeled “PWR IN” instead of “USB” – you should see a small green LED flickering.

Our Pi Zero W is now booting, enabling SSH access, and connecting to the wireless network we specified in the file above. We need to know the IP address that DHCP assigns to it. There are a few ways to do this, but it’s simplest to just login to your router configuration (assuming your DHCP is hosted there) and look for a device named Raspberry Pi.

I login to my Ubiquiti EdgeRouter X and go to my DHCP area. Essentially, you’re looking for your DHCP client list. This varies based on router manufacturer. I used to have an Apple Airport Extreme and it didn’t even allow you to view your own DHCP client list (laughable). In that case, you could use a software tool like N-Map to scan your network and identify your Pi.

I went ahead and reserved the IP .82 for the PiHole. You should do something similar. Best practice would be to move the Pi outside of my DHCP client scope and configure it (client side) to a static IP address.

Now, armed with our Pi’s IP address, we’ll open Putty and connect to it via SSH:

You may get a security warning here, click Yes to proceed. If you get a “login as:” screen, you’re golden.

The default login credentials are: User – Pi  / Password – Raspberry

You can (and should) change the “Pi” user’s password by running “passwd”

Run “sudo raspi-config” and choose Update, let the utility update itself.

Let’s also get the latest updates for Raspbian by running:

sudo apt-get update
sudo apt-get dist-upgrade

Now, finally, we can install PiHole by running:

curl -sSL | bash

After some time, you’ll be greeted with this screen:

Follow the prompts. I use OpenDNS for upstream provider. IPv4 is default and most likely what you’ll want to operate on. Since I reserved in DHCP, I will tell it to keep its current address and configure itself with that as a static address.

Leave the rest of the values as default (logging, web interface)

At the end of the install, make sure to note the default login password:

You can close putty and your SSH session now. Hop over to your browser and visit your PiHole’s web interface. Mine is

If you forgot to save the default password, you can change it by opening an SSH session and running “pihole -a -p”.

There are a lot of areas to explore in the web interface, but at this point — you have a functional PiHole DNS ad-blocker with a basic list of 125,000 or so blacklisted domains.

You can put this into production now by configuring your DHCP server to assign clients to the PiHole’s address for DNS resolution.

This will differ from router to router, here’s how I do it on Ubiquiti hardware.. notice that I am using PiHole as the first DNS preference, and OpenDNS’s IP directly for some redundancy should my spiffy new $10 network appliance fail.

On a client PC, you will likely have to wait for the lease to expire or DHCP to notify the clients of the DNS configuration change. I haven’t tested how long this takes. You can trigger a refresh with “ipconfig /renew” on a client PC, then “ipconfig /all” should show your PiHole’s IP in the first DNS entry afterward.

That’s it. All of your clients will begin sending DNS requests through the PiHole now and the PiHole will actively block requests to known blacklisted addresses. There are various sites online to get additional lists to add to your PiHole, but the basic list does a decent enough job to get you started.

Bringing Clients Online: Software Update-Based Installation

When it comes to deploying the client to domain-joined devices, you’ve got a few choices. Software Updates, Client Push, or custom script. Deploying via software updates is definitely my preference, as any machine joined to the domain will get the SCCM client package pushed via the WSUS server the client device is pointed to via GPO.

Assuming we already have a healthy SCCM environment with a Software Update Point role somewhere, it’s a straightforward process. For proper usage of this client-deployment strategy, you’ll also want to verify that you have extended your AD schema, published your site to your AD forest (from SCCM console), and created at least a single boundary and boundary group configured to be used for site assignment. Else, your clients will not find a source from AD for the package nor a site code/MP to be assigned to during install.

You need to publish the client package to WSUS through the console. It generally will take a few minutes before both version boxes populate.


Next, you’ll need to configure a GPO to point targeted machines to WSUS. Be sure to include the port designation in the path, else you’ll likely see errors in the Windows Update checking process once the client processes the new GPO. Ask me how I know.

You can hop on a targeted system, run “gpupdate”, and verify that this policy applies with gpresult. Opening Windows Update and clicking “Check For Updates” should show that updates are “being managed by your administrator”, and if all goes well- you should have one update available, that update being the SCCM client package.


Once these steps are completed, you’ll have live and active clients to manage, and they’ll receive their Windows Update items through Software Center alongside your other SCCM deployments.


Some guidance on Software Update Points

I’ve heard some confusion, especially from people who are just starting to implement Configuration Manager in their environment, over the SUP role and how it looks in practice.

Obviously, you’re under no obligation to use the WSUS integration or Software Updates functionality in SCCM. You can continue to use your standalone WSUS, but in the eyes of a user, I’d much rather find my Windows Updates in the same place and being deployed with the same constraints as other applications and packages being released for my machine.

When you add the WSUS role to your target server, you’ll want  to complete only the initial configuration window where you’re asked where to store the updates content. Don’t proceed with the next bit, which will have you choose classifications and such. All of this is to be done within the console. The last time I did a rebuild, with v1607, I found that I had to perform a manual synchronization with Microsoft Update once after adding the role.

Once that’s done, you can add the Software Update Point role to your site server in the console. In my last corporate environment, this process was repeated for the CAS and three primary sites. The primary sites were configured to synchronize with the CAS, so essentially, CAS communicated with Microsoft Update and notified downstream/child servers when it retrieved new items. The idea here is that your WSUS database is complete, and then you can narrow down product selection and update classification from the SCCM console.  This is done during the addition of the Software Update Point role.

It’s a good idea to enable the WSUS cleanup tasks (I have had to deal with the results of not doing this), as well as enable alerts on synchronization failures so that you can be sure that the SUP is successful in what should be a mostly-automated process when you’re done, with the help of automatic deployment rules.

You should get an understanding for the entire process from CAS Microsoft sync to client experience before you implement this in production. You’ll want to lay down your intended Sync schedules, Software Update Groups, ADRs, target collections, available/deadline preferences, and probably create staggered deployments to allow a “beta” group to receive updates prior to being dropped into full production.

Is your SCCM installation taking ages in a Hyper-V guest?

Rebuilding the SCCMChris lab as time permits, I ran into an issue during installation of tech preview v1703 — the installer would hang during the database setup for many, many hours. It didn’t seem to completely stall, but after a day, installation was still chugging along. Thankfully, there’s a simple solution! For your guest machine, disable “Dynamic Memory” in Hyper-V manager, uninstall the site to reverse your failed installation, then kick it off again.

“The SQL server’s Name in sys.servers does not match with the SQL server name specified during setup”

I think my non-DBA background got me on this one today. I renamed my Primary box this morning after doing my SQL 2016 installation last night. Tidied up issues in the Pre-req check for SCCM installation, so I kicked it off and came back to this:

Within the ConfigMgrSetup log, I found:

ERROR: SQL server's Name '[WIN-1NOPPABSENJ]' in sys.servers does not match with the SQL server name '[CM-PRIMARY]' specified during setup. Please rename the SQL server name using sp_dropserver and sp_addserver and rerun setup.  $$<Configuration Manager Setup><04-06-2017 15:37:01.692+420><thread=1932 (0x78C)>

No doubt, this was due to my rename. It’s unfortunate that this isn’t checked a little sooner, as you’re left to do a site uninstall before you can rerun the installation properly. This is a great error, because it’s clear and even provides the solution.

Sort of. SP_DROPSERVER ‘MININTWHATEVERTHENAMEWAS” worked just fine, but apparently SP_ADDSERVER is no longer supported in SQL 2016 (maybe even earlier?). You’re instructed to use “linked servers” instead. In SSMS, I expanded “Server Objects”, then right clicked “Linked Servers”, and clicked “New Linked Server”. I entered CM-PRIMARY as Server Name and chose SQL Server as server type… and I’m greeted with a message stating you can’t create a local linked server. Switching back, I ran:


…and it executed without issue. I restarted the SQL service for good measure.

I confirmed the change worked by running the following, which returned my new system name, CM-PRIMARY:


I was then able to uninstall the site server and rerun the install again, this time successfully.

Implementing Microsoft’s Local Administrator Password Solution

Many environments I’ve worked in fall into the same habit. They set the same local administrator password on all client systems across the domain and rarely, if ever, reset it. When you consider the number of ex-employees that have that password and knowledge of the fact that all non-servers sometimes use it, coupled with the potential for Pass-The-Hash attacks, you see quickly why Microsoft created the Local Administrator Password Solution. It’s really easy to implement. Easy enough that the documentation alone will probably get you there. Regardless, here’s my guide for implementation. As usual, your mileage may vary.

On your system, you’ll need to install the LAPS package with the management tools component to have the appropriate PS cmdlets and GPO template.
Download LAPS here:

Choose to install management tools (and GPO extension if you intend to apply LAPS to the system you’re working from)

We need to accomplish 5 things to successfully deploy LAPS. Adjust paths as necessary, mine used as an example. I would suggest going through all of the motions with a test OU and a couple of test systems before deploying to a broad range of systems.

1. Extend the AD schema. This is a forest level change and cannot be reversed.

Import-Module AdmPwd.PS

2. Allow computers in target OU(s) to update their password fields.

Set-AdmPwdComputerSelfPermission -OrgUnit “OU=Computers,DC=sccmchris,DC=com”

3. Allow specific users to retrieve the content of password fields for computers in target OU(s). Here, let’s assume we have a group called “Desktop Support Staff” and we’d like members of that group to be able to retrieve local admin passwords for any system within the Computers OU.

Set-AdmPwdReadPasswordPermission -OrgUnit "OU=Computers,DC=sccmchris,DC=com" -AllowedPrincipals "Desktop Support Staff"

4. Configure GPO and link to appropriate OU. Below is my configuration. Note: Until you enable the setting “Enable local admin password management”, regardless of extension install or GPO application, nothing will be changed in re: to local admin password. If you leave “Name of admin account to manage” not configured, it will manage the default Administrator account. This is nice because you can roll the client MSI in advance of actually enabling LAPS.


5. Deploy LAPS CSE (client side extension) on target systems. This is the same MSI that you used to install the management tools. If you run this MSI with the silent switch, it will install only the GPO extension for the client (no management tools). This makes it incredibly easy to deploy in SCCM or you can even script it on non-SCCM clients.

Only once a client has the GPO extension installed, the GPO applied, and the “enable local admin password management” setting enabled, the management will begin.

That’s it. You’ve deployed LAPS. Of course, you’ll want to do some auditing to ensure systems are successfully submitting their passwords. Options for reading back local passwords: 

1. The MSI’s management tools component includes a LAPS UI for retrieving local admin passwords and forcing resets.

2. I use a LAPS Password plugin for SCCM. Find it here:

3. PowerShell option:

Get-AdmPwdPassword -ComputerName W10L1234

4. You can retrieve the passwords for *all* computers in an OU (assuming you were granted Read). This is especially useful for your initial test deployment and verifying passwords are being submitted (accurately):

Get-ADComputer -Filter * -SearchBase “OU=Computers,DC=sccmchris,DC=com” | Get-AdmPwdPassword -ComputerName {$_.Name}


I had very few issues with this deployment. If I could give you one piece of advice it’d be to use options #4 to generate a list of systems that have submitted a password and compare it to a list of computers that have supposedly installed the client side extension already. Troubleshoot the delta. The few systems I had trouble with were generally experiencing group policy application issues. I had two systems (out of 1,000) that required manual reinstalling of the CSE.

Hardware Inventory Implosion After v1610 Upgrade!

Note: This post is adapted from my working notes, so I apologize for being a little all over the place. I didn’t find this issue described online, so I thought it was important to get something posted to hopefully save someone else the trouble.

Naturally, my first routine servicing upgrade caused an implosion of hardware inventory across the hierarchy. My first indication of an issue was the SMS_MP_CONTROL_MANAGER being in warning status in console for all MPs. Logs full of this:

I confirmed that virtually all clients had last submitted hardware inventory the night of the v1610 upgrade. My clients are set to inventory nightly, so something has to give.

I went to a client and initiated a full hardware inventory in Client Center. Confirmed the InventoryAgent.log indicated successfully collecting Hinv and submitting it to MP.

So clients are submitting inventory to the MP, but it’s not processing properly. At this point, So, let’s look at a Management Point.

Checking out (installpath)\SMS_CCM\Logs\MP_Hinv.log, it’s loaded up with these:

OK…. So there’s the date error. This has some discussion around the internet (thanks Google) but I don’t see anyone saying it’s forcing their hardware inventory to cease…

The “cache is still obsolete” is probably related to our issue. Unlike a lot of error messages, I can’t find anything specific online.

It says it is making a retry file with this Hinv submission. Let’s see how bad the retry files are.. Looking at (installdirectory)\inboxes\auth\\retry\

Not good. 5200+ files. I quickly check my other MPs and find the same.

Going back to the original error about reloading the hardware inventory class mapping table. Our Hardware Inventory is extended to include LocalGroupMembers (Sherry K.) and additionally I’ve enabled the default Bitlocker class. My impression here is that the clients are submitting these extra classes, but the site servers aren’t expecting them now.

There’s an easy way to test this… let’s take the error at face value and “trigger a HINV policy change. I hopped onto the default Client Settings policy and I disable the LocalGroupMembers class, wait a few minutes, and then re-enable it.

Giving my nearest Primary a break here – I move all of the RetryHinv files from \inboxes\auth\\retry to a temp folder called “old”.

New diagnostic clue: after this change, the “obsolete cache” errors stop appearing in the MP logs. Additionally: no more retry files are being generated. I take 8 RetryHinv files and paste them back into the retry directory. After about 10 minutes, all of them disappear. Dataldr.log shows this:
5I check Process and they’re gone, they’ve been dealt with. Fantastic. 5,000 to go. I cut 500 of these back into the Retry directory. I suspect a number of these will be rejected because they are now too out of date. This is confirmed by some of them being moved to delta dismatch directory.
6Look at that. I verified that these ran through the process folder OK. I checked the BADMIFs directories to make sure I didn’t have 500 rejected MIFs. Only a few marked as delta mismatch. I’m guessing that’s not too bad considering that these machines have been submitting and hung up completely since the 27th. I move the remaining retry files back into the retry directory….

Caught in the act- the 4,77x RetryHinv files are disappearing in front of me. Looks like they are converted from HML to .MIF and then placed back in the directory. This directory ballooned up and the logs are going nuts.

Processed in a couple of batches- “Finished processing 2048 MIFs SMS_INVENTORY_DATA_LOADER”

There are about 1,000 “deltamismatch” in BADMIF. This is almost certainly systems that have submitted multiple delta reports that have been caught in the RETRY queue for the past week. Not surprising.

I checked all other inboxes to verify I don’t have backlogs anywhere else.

In summary, the “Obsolete Cache” error looks to have generated a Retry for every client hardware inventory submission. There was this long loop because every inbound Hinv generated a retry and every retry failed (and generated a replacement retry). This explains behavior I saw earlier: all of the retry files were continuously having their “date modified” updated to within a few minutes of each other (and no more than about 15 minutes from current time). So, in short, the dataldr inbox was stuck in an endless loop trying to process Hinv submissions.

The issue was obviously caused during the upgrade. 50% of my clients are offline, so there’s no way that fixing the clients was the solution to get the server processing the Retrys (and inbound new submissions) without error. No, updating the Client Policy must have replaced a configuration file or setting somewhere that corrected the issue.

I can’t be more specific than that at this point, but I’ve got a grasp on the situation, it appears.

As expected, the date error is not a showstopper, just a warning. It also appears to be a common thing. I can visit it at a later time since it appears to have a simple fix. See description here:

36 hours later, almost all of my Active clients have Hardware Inventory Scan dates listed after the upgrade date.

Text copies of relevant messages for Google’s use:

MP needs to reload the hardware inventory class mapping table when processing Hardware Inventory.

Hinv: MP reloaded and the cache is still obsolete.