PowerShell logo in a heart

Tech talk development tips, or how to crush it like Chrissy

PowerShell logo in a heartPowerShell Conference EU 2019 was, as always, an excellent learning experience, a wonderful week with old friends, and a chance to meet new friends. My particular favorite sessions that I attended were given by “Walter Legowski” (security researcher and International Man of Mystery) on sketchy fun with PowerShell, Stephane van Gulick (classy trilingual lover of sportswear that would be garish on anyone else) on his PSHTML module and Polaris, Staffan Gustaffson (will argue your ear off in the hotel bar about epistemology if you admit to being religious) on using a profiler with PowerShell, and Daniel Silva (newcomer making IoT accessible via RaspberryPi to the old Windows server ops crowd) proving that indeed, everyone loves LEDs. I saw several more good sessions, but these are the ones that I’m still thinking about a week later.

However, the session I learned the most from was my own little lightning talk – definitely not for the subject matter (Kubernetes bare basics + the PowerShell ConvertFrom-Json trick), but for finally learning a process for developing and then refining a technical talk from the creator and maintainer of dbatools, Chrissy LeMaire. Her excellence in speaking is a major factor in that project’s success – not only in attracting users, but in building and maintaining a broad community of contributors.

Chrissy’s own process probably differs somewhat from the points I took from her guidance, but this is stuff I want to really remember and that might help others.

Tip #1: Try freestyling your session at least once before starting on slides

“Ok, now stand up and give me your talk.”

“Oh, but I’ve only started my slides – I think I’ll need another half hour before I have anything!”

”You don’t need slides yet – just give me the explanation part and then show your code so that we can see how long it currently is.”

This was the big eye-opener. I’d always opened PowerPoint first and started writing once I’d decided what code I wanted to present, planning to cut or add as needed. Chrissy had me do the reverse. She timed my “freestyle” session, and then gave me some content notes, pointing out the things I mentioned that she didn’t know much about and that my audience was also likely to need more basic info about to follow my code: Kubernetes, AKS (Azure Kubernetes Service).

Tip #2: Do not try to make one slide do the work of five, or do all of *your* work

“There’s too many damn words on that slide! There should not be several completes sentences on a slide. Split it up!”

“Your slides should keep you and the audience on track, not tell them everything they need to know.”

After giving me awhile to do my slides, Chrissy had me run through the talk again. She reminded me that the presentation I was disappointed in a few years ago was due, in great part, to my slide deck. The bigger problem there was due to there being too much on the slides, more so than the count. She encouraged me to split my backgrounder slide into five, one for each of the major points I was making, and then to just leave a few words about each topic, removing the sentences. She also pointed out where I was digressing into unnecessary detail or anecdote that was not central to my topic.

Tip #3: If you have to scroll back and forth in your demo code, re-order your code

Once we were happy with the explanatory portion of my talk, Chrissy had me go through my code again, timing me. She noticed that I was doing an awful lot of scrolling, up and down, so pointed out that re-ordering the code would make things go smoother.

After re-ordering and doing a bit of cleanup, she had me present the code portion again.

Tip #4: Think of your talk in segments and work on them separately

Breaking my 10 minute talk into two segments made developing and then practicing them less intimidating, plus gave me the confidence that I was going to have enough time for the “fun” part (code) because I knew I only needed five minutes to get through the introductory explanation. This has the added benefit of making the talk more modular: you can tailor the same basic talk for a user group that wants you to talk for 30 minutes or for a conference that has 75-minute sessions. For this particular talk, if I want to add more Azure platform information, I just have one new segment to write and learn; or conversely, I now have a five-minute snippet I could present in an even more constrained lightning talk format.

Tip #5: Practice, practice, practice… then practice again.

I didn’t practice the completed presentation as many times as Chrissy recommended (three), let alone as much as she generally does, but it was more than I have in the past, given how much I practiced while developing the slide deck and re-ordering my code.

Steve Jobs would clear his calendar for a month before a big Apple event, and I’ve seen Chrissy spend evenings practicing a talk she had written well in advance. Especially for beginning speakers, the more you rehearse, the more natural you will sound. Do not make the mistake of thinking, “if I practice it too much, I’ll sound stilted.” I’ve seen lots of presentations over the years that would have been better, and speakers who would have had a better time on stage, had they practiced it a few times, by themselves and for an audience.

Tip #6: Get peer feedback throughout the process

Feedback from a PowerShell expert new to Kubernetes, like Chrissy, was more valuable than from a Kubernetes expert unfamiliar with PowerShell, but the latter would have been a good addition to make sure my Kubernetes statements were correct. Even feedback from someone outside of IT, like my mechanical-engineer husband, would have been more helpful than developing the talk on my own. It’s much like having a classmate edit your drafts back in English class: you might not incorporate or even agree with all of their feedback, but having the feedback available throughout the process makes it more useful.


For a talk created mostly the night before (after scrapping a more boring initial concept), I think it went well. It would have been better if I’d started the process earlier, allowing more time for feedback.

Things I did well this time:

  • hit all the points I wanted to discuss without digressing into less-relevant matters
  • fit it into my alloted timeslot exactly, which is a courtesy for the attendees and other speakers
  • used slides to keep me and audience focused rather than to explain details
  • sparked interest for Kubernetes in a community that I didn’t think would have much

Things I’m going to do differently in the future:

  • ask audience to hold questions for later at beginning of talk – in the moment, I answered that first question, which opened the metaphorical floodgates. I should have asked that questioner and subsequent ones to find me afterwards for details.
  • “pre-run” code so that I don’t waste our time in case someone wants to see the results (another in-session question)
  • find out what PowerShell (legacy Windows ops) people need to know about Kubernetes – I’ve been near it for so long that I’ve lost sight of what I had to learn to be a somewhat-competent administrator
  • practice, practice, and then practice some more 🙂

Thanks again to Chrissy for her good advice, encouragement, constructive criticism, positive peer pressure, and above all, patience.

See this talk for yourself, and please leave feedback in this post’s comments, both on style and content: (will add, if recording came out!)

Resources (slides and demo code): Kubernetes and PowerShell

See Chrissy “freestyling” (live-coding) on Twitch, which I’m not nearly brave enough to do: http://sqlps.io/twitch


Good news: my car’s transmission is failing!

Possibly, my last car

Focus… on your DOOM! Great on the Autobahnen, not so much for quick trips around town

On Monday evening, I dropped my 2008 Ford Focus diesel wagon at the dealer to get the timing belt changed and other 180,000 km/10 yr maintenance done and winter tires put on, with a request that they call if it was going to be over 100 EUR more than the 1350 EUR on the quotation.

On Tuesday morning, they called to tell me that the trouble starting I reported was actually to do with the transmission, and they had the failure codes from the ODBC to prove it, but were not sure that doing the 450 EUR transmission oil change would fix it, my first reaction was panic, assisted by already panicking over what my fellow Americans would do that day at the polls.

But then I remembered that I had ridden to work with my colleague, and was planning to take the U-Bahn and walk to the dealer to pick up my car. In fact, I drive to work about once per month and to the grocery store most Saturdays. I could buy another 10 year old Focus wagon for about 4000 EUR, if it turned out I really did miss having my own car.

On Tuesday afternoon, I asked for a purchase offer; the most the dealer was willing to pay was 650 EUR, because my car is pretty much unsaleable in Germany (older diesel + automatic transmission). Instead of spending the minimum of 1800 EUR for the scheduled maintenance and attempt at fixing the transmission, I paid 75 for the initial diagnosis and installation of my winter tires, with the intention of driving it until the transmission really did go.

On Wednesday morning, as I drove home from the dealer, I saw all those sketchy-looking used car lots, promising cash purchase for all cars…

Continue reading

Docker for Windows behind a corporate web proxy: tips and tricks

Why setting the http_proxy and https_proxy environment variables has no effect on Docker for Windows (or, why there is no docker.ini or Windows Registry setting for this)

Docker for Windows allows you to use docker commands from cmd or PowerShell as if the Docker host were running locally on your PC. This is a clever illusion: while docker.exe is a Windows program, the Docker host you’re connecting to is really running in a Hyper-V virtual machine named MobyLinuxVM. So far, I have not found a way to SSH into this VM; had I done so, I could have set the HTTP_PROXY and HTTPS_PROXY environment variables by PowerShell script every time my PC’s IP address changed.

Caveats before beginning

I use this setup on Windows 10 and Docker for Windows 17.06. A colleague still using Docker for Windows 17.03 reports that he has over 20 instances of the DummyDesperatePoitras virtual switch, so if you’re not on 17.06 yet, I recommend updating.

The IP address (, DNS host ( and domain (mandie.net) are DEFINITELY not the right options for your configuration, and are just here to make the examples easier to read. Do NOT just cut and paste things from this page – but you shouldn’t do that anyway 😉

DummyDesperatePoitras: this weird Docker for Windows artifact turns out to be key

The Hyper-V DummyDesperatePoitras virtual switch gets made when the Docker virtual switch is. This is a workaround for some issue or other. It turns out to be extremely useful! I’d tried using the Docker virtual switch’s IP address as the proxy address, but that didn’t work.

DummyDesperatePoitras gets a random, non-routable ( IP address.

Get that IP address using PowerShell (no need to be admin):

(GetNetIPAddress -InterfaceAlias "*DummyDesperate*" -AddressFamily IPv4).IPAddress

For this example, I’ll use as the result of this command. Your randomly-generated IP address will almost certainly be different.

If you get more than one result, it might be due to the problem described earlier in this post that some older versions of Docker for Windows had, where the Docker virtual switch was removed every time and remade, without removing the DummyDesperatePoitras, but still being made every time the Docker virtual switch was made. As mentioned earlier, try uninstalling Docker for Windows (removing the MobyLinuxVM will remove any images or containers you’ve created), remove all the DummyDesperatePoitras virtual switches, and then installing the 17.06 or later version.

Just enough CNTLM

CNTLM is an executable primarily available on SourceForge (now under new management and no longer dispensing spyware with its downloads, but still…) that has not been updated in over 5 years. It runs as a service, which requires local admin. It goes against all my principles.

It is also the only practical way to use Unix-style command line tools with NTLM-authenticating corporate web proxies.

Promising alternative: the Python-based Windows proxy px (https://github.com/genotrance/px). It has the major advantage of not requiring your Windows password in any form, and the major disadvantage of needing to understand more than I do about Python to build and run it. If you manage this feat, please tell me about it, and better yet, write your own blog post about how you pulled it off.

Listening on or just 3128 and setting as your proxy will not work for Docker for Windows, because the guest VM that is really your Docker host interprets that address as *itself*, not your Windows host running CNTLM.

Having it listen to and setting the PC’s IP address as the proxy works, but you have to then change your Docker settings any time your PC gets a new IP address – a routine occurrence for consultants or anyone moving around a large campus. It also means that your PC will act as a web proxy for ANYTHING that isn’t blocked by its firewall. This can be either a bug or a feature, but I officially recommend that you do not use it as a proxy for things outside of your desktop.

The advantage of having CNTLM listen on a non-routable but static IP address is that only traffic that originated on your PC can use the proxy, but you still don’t have to change it when your PC’s IP address changes.

CNTLM configuration is set in cntlm.ini, under C:\Program Files (x86)\cntlm. The easy way to use it is to set your Windows password directly in the file. That is also the terrible way to use it. If you store the NTLMv2 hash, at least it can’t be used anywhere other than your PC (which is still not great). Set your cntlm.ini file to only be readable to the account the cntlm Windows service is running under.

Here is a way to get your NTLMv2 hash: https://stackoverflow.com/a/44238035

Set CNTLM to listen for (DummyDesperatePoitras IP) 3128 – in this example:

Listen 3128

Remember to set your NoProxy subnets here: usually, 127.0.0.*, 10.*, 172.16.*-172.31.* and 192.168.*, but you might have others that shouldn’t go via the corporate web proxy.

Set your http_proxy and https_proxy in Windows for other command line tools

While testing, set it at the command line for your current session – note the http:// (not https://) for both.

For cmd:

set http_proxy=
set https_proxy=


$env:http_proxy = ""
$env:https_proxy = ""

This will only have effect for this session, and only as long as you have the window open.

Once you’re sure this works, set them in your profile’s environment variables. This change will not take effect until you log off and log back on to Windows.

Docker for Windows settings: Proxies and daemon.json

Right-click the little whale icon in the system tray and select “Settings…”, then click “Proxies”

HTTP and HTTPS Proxy Settings in Docker GUI

Docker Proxy Settings

In the “Web Server (HTTP)” blank, type (replace with the IP address you got from running the Get-NetIPAddress cmdlet at the beginning) and click the “Use same for both” box. You should set your proxy bypass addresses in cntlm.ini for consistency between all the command line tools you use.

Click “Apply” – this is necessary to save your changes (writing them to MobyLinuxVM), but will also restart Docker (MobyLinuxVM). Go get a coffee – this takes a minute or two.

Next, click “Daemon” so that we can give it a DNS server to use for your internal domains. Click the “Experimental features” checkbox and the “Basic” switch so that it turns to “Advanced”

Docker Daemon screen before daemon.json editing is enabled

Docker Daemon – Enable daemon.json editing

Add entries for dns and dns-search, remembering to separate each entry with a comma.


Your resulting JSON file should look like this, but with your internal domain and DNS server’s IP address instead of mandie.net’s:

 "registry-mirrors": [],
 "insecure-registries": [],
 "debug": true,
 "experimental": true,
 "dns": [
 "dns-search": [

Click “Apply,” and wait that minute or two again.

Testing, and extra help for docker build

Were we successful? Open cmd or PowerShell, and try pulling the hello-world image. Even if you have the latest version already, this goes out to docker.io and checks.

docker pull hello-world:latest

This configuration should be sufficient for pulling Docker images and running containers on your Docker host. However, any image builds that pull components from outside (apt-get, npm, etc.) will need to get the proxy information explicitly. You don’t want to store this in your Dockerfiles, since it’s specific to your PC. You can feed build-time environment variables like this:

docker build --build-arg http_proxy= --build-arg https_proxy= -t myawesomeimage:latest .

Let me know if something about this doesn’t work for you – I’m still a bit surprised that it works at all.

Now for something completely different: offline patching for IBM Cloud private expired product certificate

In case you were wondering why I’ve not posted any silly Skype tricks lately, I seem to have ended up in charge of Kubernetes at my company, and for reasons I won’t bore you with, we’ve decided to set up IBM Cloud private on-prem.

The product certificate expired on 22 August 2017, so there’s a patch: https://www.ibm.com/developerworks/community/blogs/fe25b4ef-ea6a-4d86-a629-6f87ccf4649e/entry/Certificate_update?lang=en

However, the included bash scripts presume that all of the nodes have access to Docker Hub. Since ours don’t, because we are not heathens who allow our servers to talk to the Internet, I made a workaround:

1) Get the updated ibmcom/cfc-router:1.2.0 image using a machine that can connect to Docker Hub and pack it into a tarball:

docker pull ibmcom/cfc-router:1.2.0
docker save ibmcom/cfc-router:1.2.0 -o cfc-router-fix.tar

(rant about pushing an updated Docker image with THE SAME EXACT VERSION TAG goes here)

2) Copy this tarball to all of the nodes in your cluster

3) SSH into each node in the cluster and load the image:

docker load -i /wherever/you/put/it/cfc-router-fix.tar

4) Copy the update-cert.sh script and remove the following lines, because they are the ones that wanted to go onto the scary Internet and pull the images directly. Leave in all the other docker commands! If you’re using Windows, copy it to a Linux machine and edit it there to avoid any weird linefeed stuff:

if [[ "$(uname -m)" == "x86_64" ]]; then
    docker pull ibmcom/cfc-router:1.2.0
    docker pull ppc64le/cfc-router:1.2.0

5) Copy your modified script to each of the nodes. You then need to set execute permission on it, and can finally run it:

chmod +x update-certs.sh


What Version Are Your AudioCodes SBA Web Interfaces? A One-Liner.

How your AudioCodes SBA login pages should look, as of May 2016

How your AudioCodes SBA login pages should look, as of May 2016

Unlike the components the SBAs share with their big Front End Server brothers, like RTCSRV and RTCMEDSRV, the manufacturer-custom web interfaces are NOT updated in the Cumulative Updates. You need to check on these periodically with AudioCodes, Sonus or whoever else you got your SBAs from.

These management interface updates are for security and performance issues. If you’re running your SBAs the way the manufacturer recommended, though, there are a lot of remote operations that just won’t work, making version checking painful.

However, if you’ve got AudioCodes SBAs, here is a one-liner that only requires that you have a consistent naming convention (we have “sba” in all of our SBA names) and at least ViewOnlyAdmin access to Lync/Skype, using the magic of very simple webscraping:

(Get-CsPool).computers.where({$_ -like "*sba*"}) | foreach { (Invoke-WebRequest -Uri "http://$_/Home/LogOn").content -match "(1\.\d+\.\d+\.\d+)" | out-null; [pscustomobject]@{ComputerName = $_; Version = $matches[0] } }

Substitute whatever your SBAs have in common for “*sba*” (remember the asterisks!)

Or, here’s a version using nested Where() expressions that will work even if you have no naming conventions:

(Get-CsPool).where({$_.services -like "*registrar*" -and $_.services.where({$_ -like "WebServer*"}).count -eq 0}).computers | foreach { (Invoke-WebRequest -Uri "http://$_/Home/LogOn").content -match "(1\.\d+\.\d+\.\d+)" | out-null; [pscustomobject]@{ComputerName = $_; Version = $matches[0] } }

I have no idea if a similar approach will work with other manufacturers’ SBAs.