Bookmark and Share

Hey, Scripting Guy! Question

Hey, Scripting Guy! Is it very difficult to use Windows PowerShell 2.0 to write to the Windows registry? We have a mixture of Windows 7, Windows Vista, and Windows XP on our desktop computers. I was thinking that if it is not too tough, I would like to use the registry to help in performing desktop audits. I could place information about checks in the registry, and later read that registry key to see if the check has completed. Will this work?

-- RG

 

Hey, Scripting Guy! AnswerHello RG,

Microsoft Scripting Guy Ed Wilson here, the 2010 Scripting Games are winding down—we are kicking off the wrap-up today. Beginner Event 1 was a task that required checking for the existence of a registry key, and creating it if it did not exist, or merely update the value if it did exist.

For a good introduction to using Windows PowerShell to work with the registry, see The Scripting Wife, Windows PowerShell, and the Registry. For some more advanced topics, check out some of the other registry articles in the Hey, Scripting Guy! Blog archives. There you will find articles such as changing default values of registry keys, listing user profiles, recently run programs, or changing browser history settings.

This scenario sounds exactly like the one you described. For Event 1 in the 2010 Scripting Games, a particular registry key was specified. The registry key is seen in the following image.

Image of registry key from Event 1 of 2010 Scripting Games

Let’s take a look at a script that was submitted by RKusak. In reality, I do not consider this to be a beginner script because of the use of help tags and cmdlet binding. However, it is a best practice to use help tags because it allows your scripts and your functions to interact with the Get-Help cmdlet from Windows PowerShell.

Use of help tags was discussed in a Hey, Scripting Guy! Blog post back in January 2010.

This is a tremendous advantage for several reasons. The first is that it simplifies your coding—you do not have to define help parameters, and code checks for the existence of those help parameters. The second advantage is that it allows your script or function to behave just like standard Windows PowerShell cmdlets; therefore, users will automatically know how to use your script. Last, it makes your script easier to read because when you see the help tags, you immediately know what the code does, and you will not need to study the code to figure out what the writer of the script is trying to accomplish.

 

<#
.SYNOPSIS
Sets a registry key.
.DESCRIPTION
The SetRegistryKey function replaces a value on an existing key or
creates a key that contains a value.
.PARAMETER Key
The registry key path.
.PARAMETER Name
The name of the registry property.
.PARAMETER Value
The registry key property value.
.PARAMETER Type
The registry property type.
.EXAMPLE
SetRegistryKey -Key 'HKCU:\Software\Foo' -Name Bar -Value null
Sets the registry key "HKCU:\Software\Foo" property "Bar" to the value
"null"
.NOTES
Name: SetRegistryKey
Author: Rich Kusak
Created: 2010-05-01
LastEdit: 2010-05-01 22:37
#Requires -Version 2.0
#>

One thing, I should point out is that although this script only has the help tags in the function, you can also place them at the top of the script so that you would be able to use the Get-Help cmdlet to retrieve help for the entire script. You can therefore add help tags to a script and to each function in the script if you wish. If you anticipate using a function in other scripts or perhaps one day incorporating the function in a module, you should add help tags to the function. On the other hand, if you do not foresee such a case, but rather have used the functions to group your code to make it easier to read and to maintain, add the help tags to the top of the script, and do not bother with adding them to your functions.

One improvement to the help section would be to add additional examples of syntax usage. As a best practice, you should include an example of using each parameter, as well as different combinations of parameters. If you use realistic values for the parameters, it makes the examples even better. For example, there is no foo or bar registry key, and a prospective script user might not understand the relationship between registry keys and property value names. An example that uses a common registry entry will make that clear.

The script uses CmdletBinding and specifies specific positions for the first three parameters. The last parameter is used to identify the type of registry value that is being assigned. This would allow you to call the function and supply the values for the function. There is not a mechanism to supply these values from the command line when calling the script. To use this function, you need to incorporate it into another script, function library, or module. If you dot-source it into a Windows PowerShell console, you could use the function, but the code outside the function will also run and therefore will create the scriptingguys registry key on your computer. You would not need to use the parameter name because you could call the script using positional parameters. If the script had been designed to accept piped input, it would have been even more useful. You could then use the Get-Content cmdlet to read a text file, and pipe the results to the cmdlet to make a series of changes. The parameter section of the function is seen here:

[CmdletBinding()]
param (
[Parameter(Position=0)]
[System.String]$Key,
[Parameter(Position=1)]
[System.String]$Name,
[Parameter(Position=2)]
[System.String]$Value,
[Parameter()]
[Microsoft.Win32.RegistryValueKind]$Type
)

One thing to keep in mind when using the Test-Path cmdlet is that it will verify the existence of a registry key, but not the registry key property. In the Event 1 scenario, the 2010ScriptingGames registry key will return True if it exists and False if it does not exist. However, the LastUpdate property will always return False even if it does exist. Therefore, the function will verify if 2010ScriptingGames exists, but not LastUpdate. By using New-ItemProperty with the –Force parameter, the LastUpdate property will be created if it does not exist, or it will be updated if it does exist. If the –Force parameter was not used, an error would be returned if the property already existed. Set-ItemProperty will update a property that exists, but if the property does not exist, it returns an error. The code that works with the registry is shown here:

if (-not (Test-Path $Key)) {
New-Item $Key -ItemType Registry -Force | Out-Null
}
New-ItemProperty $Key -Name $Name -Value $Value -PropertyType $Type `
-Force | Out-Null

The entry point to the script calls the SetRegistryKey function. It is a best practice to use the verb-noun combination. Instead of calling this function SetRegistryKey, it should have been named Set-RegistryKey. If this function is ever placed into a module, it will need to be renamed, or else a warning message will be displayed when the module is imported into a Windows PowerShell session. In addition, I generally prefer a more significant differentiation between the end of a function, and the entry point of the script. I generally do this in two ways. I add a tag to the closing curly bracket of the function with the function name, and I add a comment indicating the entry point to the script. Here is the way I would have done that for this script:

} #end Set-RegistryKey Function
# *** Entry point to script ***

This makes the code a bit easier to read, especially if it is a very long script with many functions. Some editors provide the ability to collapse functions and create regions, which makes these conventions unnecessary. You need to convert the datetime object into a string. This script uses the format parameter to select the month, day, and year separated by forward slashes. The hour, minute, and seconds are displayed with colons as separators. The tt means that “A.M.” or “P.M.” will be displayed as appropriate. This is shown here:

PS C:\> Get-Date -Format 'M/d/yyyy hh:mm:ss tt'
5/14/2010 02:52:09 PM
PS C:\> Get-Date -Format 'M/d/yyyy hh:mm:ss'
5/14/2010 02:52:15
PS C:\>

This section of the script that writes the date to the registry is shown here:

SetRegistryKey -Key 'HKCU:\Software\ScriptingGuys\2010ScriptingGames' `
-Name 'LastUpdate' -Value (Get-Date -Format 'M/d/yyyy hh:mm:ss tt')

The complete Beg1_RKusak.ps1 script is shown here.

Beg1_RKusak.ps1

function SetRegistryKey {
<#
.SYNOPSIS
Sets a registry key.
.DESCRIPTION
The SetRegistryKey function replaces a value on an existing key or
creates a key that contains a value.
.PARAMETER Key
The registry key path.
.PARAMETER Name
The name of the registry property.
.PARAMETER Value
The registry key property value.
.PARAMETER Type
The registry property type.
.EXAMPLE
SetRegistryKey -Key 'HKCU:\Software\Foo' -Name Bar -Value null
Sets the registry key "HKCU:\Software\Foo" property "Bar" to the value
"null"
.NOTES
Name: SetRegistryKey
Author: Rich Kusak
Created: 2010-05-01
LastEdit: 2010-05-01 22:37
#Requires -Version 2.0
#>
[CmdletBinding()]
param (
[Parameter(Position=0)]
[System.String]$Key,
[Parameter(Position=1)]
[System.String]$Name,
[Parameter(Position=2)]
[System.String]$Value,
[Parameter()]
[Microsoft.Win32.RegistryValueKind]$Type
)
# Test for the registry key path and create if the test is false
if (-not (Test-Path $Key)) {
New-Item $Key -ItemType Registry -Force | Out-Null
}
# Creates or replaces the registry property value
New-ItemProperty $Key -Name $Name -Value $Value -PropertyType $Type `
-Force | Out-Null
}
# Invoke the function with parameter arguments for the 2010 Scripting Games
# Event 1
SetRegistryKey -Key 'HKCU:\Software\ScriptingGuys\2010ScriptingGames' `
-Name 'LastUpdate' -Value (Get-Date -Format 'M/d/yyyy hh:mm:ss tt')

The Advanced Event 1 of the 2010 Scripting Games, added the requirement to edit the registry of a remote computer to the scenario. WYN submitted the Adv_WYN.ps1 script that uses the help tags for the script itself, it also creates command line parameters. This allows you to obtain help without having to open the script and read the comments. In addition, you can control the way the script operates without having to directly edit the script. The functions and the main portion of the script are clearly delineated by comments. The functions adhere to the verb-noun naming convention, and standard verbs such as new and set are used in creating their names.

Adv1_WYN.ps1

<#
.SYNOPSIS
Updating and creating registry keys on local as well as remote computers.
.DESCRIPTION
This script is for 2010 Scripting Games Advanced Event 1.
.LINK
http://bit.ly/bZcXD2
.SYNTAX
AdEvent2-RetrieveWorkstationStartTime.ps1
.PARAMETER ComputerName
A computer name or a list of computers (separated by comma) on which user
can create and update the registry. This parameter cannot be used together with
FilePath parameter.
.PARAMETER RegHive
A string value that specifies the registry hive such as "LocalMachine",
"CurrentUser", etc. The default value is "CurrentUser"
.PARAMETER KeyPath
The registry path. It is a string value, and its default value is:
"Software\ScriptingGuys\2010ScriptingGames"
.PARAMETER RegName
The registry value name (in Windows PowerShell, it is call the name of the registry
key's property). The default value of this parameter is: LastUpdate.
.PARAMETER RegValue
This is the value of the registry value (in Windows PowerShell, it called the value
of registry key's property). The default value of this parameter is: the
date and time that the registry value being updated (string).
.PARAMETER RegValueType
The registry value type. The default value for this parameter is: String.
.PARAMETER FilePath
The full name of the text file that contains a list of computer names. This
parameter cannot be used together with the ComputerName parameter. No
default value is defined.
.EXAMPLE
.\ADV1_WYN.ps1
This will create or update the registry with default value (LastUpdate) in
default key (HKCU:\\Software\ScriptingGuys\2010ScriptingGames) for the
default computer (local computer).
.EXAMPLE
.\ADV1_WYN.ps1 -ComputerNames Computer1,Computer2
This will create or update the registry with default value in default key
for all the listed computers
.EXAMPLE
.\ADV1_WYN.ps1 -FilePath .\ComputerNames.txt
This will create or update the registry with default value in default key
for all the computers listed in the file.
.EXAMPLE
.\ADV1_WYN.ps1 -RegHive "LocalMachine" -KeyPath "SOFTWARE\My Management
Tools\Windows 7 Tools" -RegName "License Key" -RegValue "9800-456-IKJP-3434"
This will create or update the registry value name (license key) with the
value ("9800-456-IKJP-3434") for the local computer
under the key path: "HKLM:\SOFTWARE\My Management Tools\Windows 7 Tools"
.NOTE
AUTHOR: WYN
CREATED ON: May 02, 2010
ADV1_WYN.ps1
# HSG-5-31-10
#>
# Requires -Version 2.0
[CmdletBinding()]
param (
[Parameter(Mandatory=$False)]
[string[]]$ComputerName = @(),
[Parameter(Mandatory=$False)]
[string]$RegHive = "CurrentUser",
[Parameter(Mandatory=$False)]
[string]$KeyPath = "Software\ScriptingGuys\2010ScriptingGames",
[Parameter(Mandatory=$False)]
[string]$RegName = "LastUpdate",
[Parameter(Mandatory=$False)]
[string]$RegValue,
[Parameter(Mandatory=$False)]
[string]$RegValueType = "String",
[Parameter(Mandatory=$False)]
[string]$FilePath
)
# /////////////////////////////////////////////////////////////////////////////
# // FUNCTIONS
# /////////////////////////////////////////////////////////////////////////////
function New-RegKey {
# Create subkey in specified registry hive/key.
param (
[string]$Key
)
# Check if key exists or not:
if ($reg.GetSubKeyNames() -notcontains $Key) {
Try {
# The key DOES NOT exist, so create it:
$Script:reg = $reg.CreateSubKey($Key)
Write-Host "`tRegistry key ""$($Script:reg.Name)"" has been
successfully created." -foregroundColor Green
}
Catch {
Write-Host "`tERROR:
$((($ERROR[0].Exception).InnerException).Message)." `
-ForegroundColor Yellow
}
}
else {
Write-Host "`tRegistry key ""$Key"" already exists. Do not need
to create." -ForegroundColor White
# Open the subkey in writable mode and update the $reg in script scope:
try {
$Script:reg = $reg.OpenSubKey($Key, $Writable)
}
catch {
Write-Host "`tERROR:
$((($ERROR[0].Exception).InnerException).Message)." `
-ForegroundColor Yellow
}
}
}
function Set-RegValue {
param (
[string]$RegName,
[string]$RegValue,
[string]$RegValueType
)
# Create or update the specified registry value (valueName and valueValue):
# First check to see if the value name exists or not in the current key:
if ($reg.GetValueNames() -notcontains $RegName) {
# Registry value does not exist, so create one and assign value:
Try {
$reg.SetValue($RegName, $RegValue, $RegValueType)
"`tRegistry value name ""$RegName"" does not exist, so create it
and assign value ""$RegValue"" with type: ""$RegValueType""."
}
Catch {
Write-Host "`tERROR:
$((($ERROR[0].Exception).InnerException).Message)." `
-ForegroundColor Yellow
}
}
else {
Try {
"`tRegistry value name ""$RegName"" already exists, so update
its value ""$RegValue"" with type: ""$RegValueType""."
$reg.SetValue($RegName, $RegValue, $RegValueType)
}
Catch {
Write-Host "`tERROR:
$((($ERROR[0].Exception).InnerException).Message)." `
-ForegroundColor Yellow
}
}
}
# /////////////////////////////////////////////////////////////////////////////
# // MAIN PROGRAM
# /////////////////////////////////////////////////////////////////////////////
$Writable = $True
# Validate parameters:
# Valid registry hive name:
$regHives = [Microsoft.Win32.RegistryHive] |
Get-Member -Static -MemberType property |
ForEach-Object {$_.Name}
if ($regHives -contains $RegHive) {
"$RegHive is valid hive name"
}
else {
$strMessage = "`r`nRegistry hive name: ""$RegHive"" is not valid.
Valid registry hive names are:"
foreach ($Hive in $RegHives) {$strMessage += "`r`n`t$Hive"}
$strMessage += "`r`nPlease try again.`r`n"
Write-Host $strMessage -ForegroundColor Red
exit
}
# Validate registry key path. The valid name should not contain the registry
# hive name:
if (($KeyPath -match '^\w.*:\\\\') -or ($KeyPath -match '^[a-zA-Z].*_\w.*\\')) {
# In valid registry key:
Write-Host "`r`nThe parameter KeyPath (registry key path) should not
contain registry hive name ""$($matches[0])"". Please try again.`r`n" `
-ForegroundColor Red
Exit
}
# Validate FilePath and ComputerName. User cannot supply both of them to
# the script:
if (($ComputerName.Count -gt 0) -and ($FilePath.Length -gt 0)) {
# User provided ComputerName and FilePath at the same time:
Write-Host "`r`nYou cannot provide ComputerName and FilePath at the
same time. Please try again.`r`n" -ForegroundColor Red
Exit
}
elseif (($ComputerName.Count -eq 0) -and ($FilePath.Length -eq 0)) {
# User did not supply ComputerName and FilePath, so set the local
# computer as the default value for ComputerName:
$ComputerName = @($Env:ComputerName)
}
elseif (($ComputerName.Count -eq 0) -and ($FilePath.Length -gt 0)) {
# User provided FilePath only, so check to see if the file exists:
if (!(Test-Path -Path $FilePath)) {
# Cannot find the file:
Write-Host "ERROR: Could not find the file ""$FilePath"". Please check
the file name and its location and try again." -ForegroundColor Red
exit
}
else {
# File exists. So get the computer names from the file:
$ComputerName = Get-Content $FilePath
}
}
foreach ($Computer in $ComputerName) {
# Check if the computer is alive or not:
Write-Host "`r`nWork with computer $Computer" -ForegroundColor Green
if (Test-Connection -ComputerName $Computer -Quiet) {
"`tComputer ""$Computer"" is alive. Start to create registry.."
# Binding to registry hive on the current computer:
$reg = `
[Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($RegHive, $Computer)
# Split the $KeyPath into string array which is the form of
# "Software\ScriptingGuys\2010ScriptingGames"
$KeyPaths = $KeyPath.Split("\")
# Create registry key for the provided $KeyPath:
foreach ($Key in $KeyPaths) {
# call New-Subkey function to create the current key if it
# does not exist:
New-RegKey -Key $Key
}
# Create or update the specified registry value (valueName and Value):
# First change the registry value type to string. It should check the
# type and change the value to that
# type first. Because of time issue, I will not do it here:
if (!$RegValue) {$RegValue = (Get-Date).ToString("MM/dd/yyyy HH:mm:ss")}
Set-RegValue -RegName $RegName -RegValue $RegValue `
-RegValueType $RegValueType
}
else {
Write-Host "Computer ""$Computer"" is not alive. So skip create
registry on it."
}
}

When the Adv1_WYN.ps1 script runs, it produces the output shown in the following image.

Image of output of running script

RG, that is all there is to using Windows PowerShell to work with the registry. The 2010 Scripting Games Wrap-Up Week will continue tomorrow.

 

If you want to know exactly what we will be looking at tomorrow, follow us on Twitter or Facebook. If you have any questions, send e-mail to us at scripter@microsoft.com or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson and Craig Liebendorfer, Scripting Guys