Setting Registry Values (Smart)

My Chrome browser was causing high CPU load. By setting a bunch of registry values, that was easy to fix - thanks to a clever PowerShell hack.

While preparing for our second day of our virtual psconf.eu mini conference, my computer suddenly started to respond sluggishly, and when I investigated, I found that my Chrome browser was causing excessive CPU loads. Specifically, a softwarereporter tool caused the issue.

Join us for more PowerShell deep dive in todays free virtual panel: see powershell.love for the details. If you missed it, go to powershell.video for the recordings.

Turned out that’s a tool Google uses to regularly scan the computer for unwanted software that may negatively affect Chromes performance. Only that this tool degraded the performance of my entire machine.

This post is not about shutting down softwarereporter which was trivial by setting a bunch of registry values. It is about setting a bunch of registry values itself.

Setting Many Registry Values At Once

You can use Set-ItemProperty to set individual registry keys:

# setting a dummy registry value (key must exist)
Set-ItemProperty -Path 'HKCU:\Software' -Name Hello -Value 'I was here!' -Type String

The parameter -Type is not undocumented as is often claimed. It is a dynamic parameter that shows only when the value you submitted to -Path makes it necessary. That’s why -Type often is missing from IntelliSense: if you are submitting a variable to -Path, then the parser can’t figure out whether -Type should be added.

Yet when you need to set more than one, your script code starts to look ugly. So why not set them all with just one call to Set-ItemProperty?

Pipeline To The Rescue

Unfortunately, the cmdlet does not accept arrays so you can’t submit more than one registry value name.

But all three parameters accept pipeline data by property name, so when you pipe objects into Set-ItemProperty that have the properties Name, Value, and Type, you can create as many registry values as you like with just one call.

Creating these input objects is simple. Here are the three values I need to turn off Chromes software reporting tool:

'Name,Value,Type
ChromeCleanupEnabled,0,DWORD
ChromeCleanupReportingEnabled,0,DWORD
MetricsReportingEnabled,0,DWORD' | ConvertFrom-Csv

The result are three objects with the required properties:

Name                          Value Type 
----                          ----- ---- 
ChromeCleanupEnabled          0     DWORD
ChromeCleanupReportingEnabled 0     DWORD
MetricsReportingEnabled       0     DWORD

Pipeline Binding Conflict

Unfortunately, you can’t pipe these objects to Set-ItemProperty even though the parameters accept binding by property name: here is my first attempt, and it emits tons of exceptions and creates weird values:

#requires -RunAsAdmin
# make sure you run this with ADMIN privileges because it writes to HKLM

# CODE BELOW FAILS! READ ON FOR THE SOLUTION...
# https://powershell.one/code/13.html

# this is where the Chrome policies are located:
$key = 'HKLM:\SOFTWARE\Policies\Google\Chrome'

# make sure the key exists:
$exists = Test-Path -Path $key
if (!$exists) { $null = New-Item -Path $key -Force }

# write the registry values:
'Name,Value,Type
ChromeCleanupEnabled,0,DWORD
ChromeCleanupReportingEnabled,0,DWORD
MetricsReportingEnabled,0,DWORD' | 
  ConvertFrom-Csv | 
  Set-ItemProperty -Path $key

The key was created, but inside I found a value called “Value”, and PowerShell complained that Set-ItemProperty was unable to convert the piped input data into the proper data types.

The reason for this was a pipeline binding conflict:

  • Set-ItemProperty has a parameter -InputObject that accepts any object by value.
  • So my own custom objects were not bound to the parameters -Name, -Value, and -Type, and instead PowerShell assigned them as object to -InputObject.

Which left me confused: why would a cmdlet have parameters that accept pipeline data by property name when at the same time another parameter would always grab all pipeline input and bind it to itself?

Do it right: Setting Multiple Registry Values

In conflicts like the one above, there is a simple trick how you can force PowerShell to bind to a parameter by property name, so this is the working example that sets the Chrome policy keys, prevents the software reporter tool and high CPU loads, and is an excellent template for whenever you need to set multiple registry values:

#requires -RunAsAdmin
# make sure you run this with ADMIN privileges because it writes to HKLM

# this is where the Chrome policies are located:
$key = 'HKLM:\SOFTWARE\Policies\Google\Chrome'

# make sure the key exists:
$exists = Test-Path -Path $key
if (!$exists) { $null = New-Item -Path $key -Force }

# write the registry values:
'Name,Value,Type
ChromeCleanupEnabled,0,DWORD
ChromeCleanupReportingEnabled,0,DWORD
MetricsReportingEnabled,0,DWORD' | 
  ConvertFrom-Csv | 
  Set-ItemProperty -Path $key -Name { $_.Name } 

Note how I just added this: -Name { $_.Name }.

Whenever a parameter accepts pipeline data by property name, it also automatically accepts a scriptblock, and $_ inside of this scriptblock represents the incoming pipeline object.

This technique was originally designed to let you quickly do adjustments to piped data. For example, if you pipe an excel sheet into a cmdlet that requires a securestring password, you could do something like this:

# illustrates how a parameter can receive a plain text pipeline input
# via binding by property name, and YOU get still a chance to convert it
# to something else i.e. a secure string before the actual binding
# takes place:
-Password { $_.Password | ConvertTo-SecureString -AsPlainText -Force }

In my case, by explicitly using the parameter -Name, I made sure PowerShell knew which parameterset I wanted to use, and happily bound the input object to the remaining parameters -Value and -Type. Amazing stuff.