One of the things I deal with a lot where I work when performing patch installations is that the number of .NET patches is just amazing! Obviously, these patches are a must to get installed, but sometimes they do not always install with the greatest of ease. The most common error I see with installing a .NET patch mentions that the rollback policy must be enabled for the installation to continue. This KB article provides more information about this as well.
The fix is to go to the registry and navigate to HKLM\Software\Policies\Microsoft\Windows\Installer and then locate the DisableRollback value name. Simply changing this value from a 1 to 0 will enable the rollback setting and the .NET patch will install without any issues.
Now this isn’t really a difficult task by any means, but it is tedious, especially if you have to do this against some 100+ systems. Can you imagine how long it would take and the tiresome clicking noise that you would have to put up with? That would drive me crazy! Enter PowerShell, the best tool for this job. Something that I say and am sure others say that if you have to do something more than once, automate it! So, with that, I put together a module of 3 commands to work with the rollback setting that can be run against one or more computers remotely so you can run the command once and move on with the installations across the network.
The biggest piece to this module that allows me to use it to make the changes lies in the Microsoft.Win32.RegistryKey namespace. The first thing we do is to create the object that makes the connection to the remote registry on a computer. For this, I look at the static methods that I can use to make this connection. The connection that stands out is the OpenRemoteBaseKey() method. This method takes two values: microsoft.win32.registryhive and the computername. Lets see what the microsoft.win32.registryhive properties are so we make sure we get the correct key.
[microsoft.win32.registryhive] | Get-Member -static -type Property
Name MemberType
—- ———-
ClassesRoot Property
CurrentConfig Property
CurrentUser Property
DynData Property
LocalMachine Property
PerformanceData Property
Users Property
Now we know what we can use with the static method to connect. We need the LocalMachine value so we can get to the DisableRollback value name.
Now I will connect to my server, DC1 and show the subkeys using the GetSubKeyNames() method from the object I created.
$computer = 'dc1'
$registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine",$computer)
$registry.GetSubKeyNames()
Using this newly created object, you can then connect to other keys within the registry and view the data within the value names. For instance, I will continue on down to find the DisableRollback value on DC1.
$subkey = $registry.OpenSubKey("Software\Policies\Microsoft\Windows\Installer",$True)
$subkey.GetValue('DisableRollback')
I used two methods to accomplish task: OpenSubKey() and GetValue() which allows me to open the subkeys and then to open the value name and get the data from that value. The $True that I added at the end of the OpenSubKey() tells that I am opening this key as Read/Write vs. the default of Read Only. This is more important if you are going to modify the data in a value or creating a subkey.
Now with that, I can change the value, or create the value name with the value using the SetValue() method. Depending on whether the value exists, you can use either two or three values with the method. In the case of my module, I use three values to create the value using the defined value and to make it as a DWORD value.
First I need to find out the correct value to place to create the right kind of key.
[Microsoft.Win32.RegistryValueKind] | Get-Member -static -type Property
Name MemberType
—- ———-
Binary Property
DWord Property
ExpandString Property
MultiString Property
QWord Property
String Property
Unknown Property
$registry.SetValue("DisableRollback",0,"DWORD")
$registry.GetValue('DisableRollback')
So that is a little background on what you can do with PowerShell to make changes to a remote computer registry values. Hopefully Microsoft and the PowerShell team will come up with come cmdlets that do this without having to go through these hoops. But in the meantime, here is an excellent module by Shay Levi that fills in that gap.
The module that I put together is only specific for working with the rollback policy, but you can most definitely take the code and adjust it to your own ideas. Below are a couple of examples with screen shots of my module in action.
Import-Module .\RollbackPolicyModule.psm1 -Verbose Get-RollbackPolicy -Computername DC1
Enable-RollbackPolicy -Computername DC1 -Passthru
Disable-RollbackPolicy -Computername DC1 -Passthru
There you have it, nothing groundbreaking by any means. But this also shows that it doesn’t matter what you decide to use PowerShell for, just as long as it helps you to accomplish exactly what you need to do, then that is all that counts.
Code
Function Get-RollbackPolicy {
<#
.SYNOPSIS
Retieves the current rollback policy on a local or remote computer
.DESCRIPTION
Retieves the current rollback policy on a local or remote computer
.PARAMETER Computername
The name of the computer or computers to perform the query against
.NOTES
Name: Get-RollbackPolicy
Author: Boe Prox
DateCreated: 06/24/2011
Links:
.EXAMPLE
Get-RollbackPolicy -Computername DC1
Rollback Computer
-------- --------
Enabled dc1
Description
-----------
Performs a query against the server, DC1 and returns whether the rollback setting has been enabled
or disabled.
.EXAMPLE
Get-RollbackPolicy -Computername DC1,server1,server2
Rollback Computer
-------- --------
Enabled dc1
Disabled server1
Enabled server2
Description
-----------
Performs a query against the server, DC1 and returns whether the rollback setting has been enabled
or disabled.
#>
[cmdletbinding()]
Param (
[parameter()]
[string[]]$Computername
)
Process {
ForEach ($computer in $computername) {
Try {
#Make initial registry connection
Write-Verbose "Making registry connection to remote computer"
$registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine",$computer)
#Connect to the subkey where the rollback is located
Write-Verbose "Making registry connection to 'Software' key"
If ($registry.GetSubkeyNames() -contains "Software") {
$subkey = $registry.OpenSubKey("Software",$True)
Write-Verbose "Making registry connection to 'Policies' key"
If ($subkey.GetSubKeyNames() -contains "Policies") {
$subkey = $subkey.OpenSubKey("Policies",$True)
Write-Verbose "Making registry connection to 'Microsoft' key"
If ($subkey.GetSubKeyNames() -contains "Microsoft") {
$subkey = $subkey.OpenSubKey("Microsoft",$True)
Write-Verbose "Making registry connection to 'Windows' key"
If ($subkey.GetSubKeyNames() -contains "Windows") {
$subkey = $subkey.OpenSubKey("Windows",$True)
Write-Verbose "Making registry connection to 'Installer' key"
If ($subkey.GetSubKeyNames() -contains "Installer") {
$subkey = $subkey.OpenSubKey("Installer",$True)
Write-Verbose "Checking to see if 'DisableRollback' value name exists"
If ($subkey.GetValueNames() -contains "DisableRollback") {
#Get the value of DisableRollback
Write-Verbose "Retrieving value of 'DisableRollback'"
$value = $subkey.GetValue('DisableRollback')
Switch ($value) {
0 {$hash = @{Computer = $computer;Rollback = "Enabled"}}
1 {$hash = @{Computer = $computer;Rollback = "Disabled"}}
Default {$hash = @{Computer = $computer;Rollback = "Unknown"}}
}
}
Else {
Write-Warning "$($computer): Missing 'DisableRollback' value name in registy!"
$hash = @{Computer = $computer;Rollback = "Missing DisableRollback value name"}
}
}
Else {
Write-Warning "$($computer): Missing 'Installer' key in registy!"
$hash = @{Computer = $computer;Rollback = "Missing Installer key"}
}
}
Else {
Write-Warning "$($computer): Missing 'Windows' key in registy!"
$hash = @{Computer = $computer;Rollback = "Missing Windows key"}
}
}
Else {
Write-Warning "$($computer): Missing 'Microsoft' key in registy!"
$hash = @{Computer = $computer;Rollback = "Missing Microsoft key"}
}
}
Else {
Write-Warning "$($computer): Missing 'Policies' key in registy!"
$hash = @{Computer = $computer;Rollback = "Missing Policies key"}
}
}
Else {
Write-Warning "$($computer): Missing 'Software' key in registy!"
$hash = @{Computer = $computer;Rollback = "Missing Software key"}
}
}
Catch {
Write-Warning "$($computer): $($Error[0])"
$hash = @{Computer = $computer;Rollback = ($error[0].exception.innerexception.message -split "`n")[0]}
}
Finally {
$object = New-Object PSObject -Property $hash
$object.PSTypeNames.Insert(0,'RollbackStatus')
Write-Output $object
}
}
}
}
Function Disable-RollbackPolicy {
<#
.SYNOPSIS
Disables the rollback policy on a local or remote computer
.DESCRIPTION
Disables the rollback policy on a local or remote computer
.PARAMETER Computername
The name of the computer or computers to disable the rollback policy on
.PARAMETER Passthru
Displays the returned object after disabling the policy.
.NOTES
Name: Disable-RollbackPolicy
Author: Boe Prox
DateCreated: 06/24/2011
Links:
.EXAMPLE
Disable-RollbackPolicy -Computername DC1 -Passthru
Rollback Computer
-------- --------
Disabled DC1
Description
-----------
This disables the rollback policy on DC1 and returns the object showing that the policy has
been disabled.
.EXAMPLE
Disable-RollbackPolicy -Computername DC1,server1,server2 -Passthru
Rollback Computer
-------- --------
Disabled DC1
Disabled server1
Disabled server2
Description
-----------
This disables the rollback policy on DC1,server1 and server2 and returns the object showing that the policy has
been disabled.
#>
[cmdletbinding(
SupportsShouldProcess = 'True'
)]
Param (
[parameter()]
[string[]]$Computername,
[parameter()]
[Switch]$Passthru
)
Process {
ForEach ($computer in $computername) {
Try {
#Make initial registry connection
Write-Verbose "Making registry connection to remote computer"
$registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine",$computer)
If ($PScmdlet.ShouldProcess("$computer","Disable Rollback")) {
#Connect to the subkey where the rollback is located
Write-Verbose "Making registry connection to 'Software' key"
If ($registry.GetSubkeyNames() -notcontains "Software") {
$registry.CreateSubKey("Software")
$subkey = $registry.OpenSubKey("Software",$True)
}
Else {
$subkey = $registry.OpenSubKey("Software",$True)
}
Write-Verbose "Making registry connection to 'Policies' key"
If ($subkey.GetSubKeyNames() -notcontains "Policies") {
$subkey.CreateSubKey("Policies")
$subkey = $subkey.OpenSubKey("Policies",$True)
}
Else {
$subkey = $subkey.OpenSubKey("Policies",$True)
}
Write-Verbose "Making registry connection to 'Microsoft' key"
If ($subkey.GetSubKeyNames() -notcontains "Microsoft") {
$subkey.CreateSubKey("Microsoft")
$subkey = $subkey.OpenSubKey("Microsoft",$True)
}
Else {
$subkey = $subkey.OpenSubKey("Microsoft",$True)
}
Write-Verbose "Making registry connection to 'Windows' key"
If ($subkey.GetSubKeyNames() -notcontains "Windows") {
$subkey.CreateSubKey("Windows")
$subkey = $subkey.OpenSubKey("Windows",$True)
}
Else {
$subkey = $subkey.OpenSubKey("Windows",$True)
}
Write-Verbose "Making registry connection to 'Installer' key"
If ($subkey.GetSubKeyNames() -notcontains "Installer") {
$subkey.CreateSubKey("Installer")
$subkey = $subkey.OpenSubKey("Installer",$True)
}
Else {
$subkey = $subkey.OpenSubKey("Installer",$True)
}
Write-Verbose "Checking to see if 'DisableRollback' value name exists"
If ($subkey.GetValueNames() -notcontains "DisableRollback") {
Write-Verbose "Creating DisableRollback value name and setting to 1 (Disable)"
$subkey.SetValue("DisableRollback","1","DWord")
}
Else {
Write-Verbose "Disabling Rollback"
$subkey.SetValue("DisableRollback","1","DWord")
}
If ($PSBoundParameters['Passthru']) {
Write-Verbose "Retrieving value of 'DisableRollback'"
$value = $subkey.GetValue('DisableRollback')
Switch ($value) {
0 {$hash = @{Computer = $computer;Rollback = "Enabled"}}
1 {$hash = @{Computer = $computer;Rollback = "Disabled"}}
Default {$hash = @{Computer = $computer;Rollback = "Unknown"}}
}
}
}
}
Catch {
Write-Warning "$($computer): $($Error[0])"
}
Finally {
If ($PSBoundParameters['Passthru']) {
$object = New-Object PSObject -Property $hash
$object.PSTypeNames.Insert(0,'RollbackStatus')
Write-Output $object
}
}
}
}
}
Function Enable-RollbackPolicy {
<#
.SYNOPSIS
Enables the rollback policy on a local or remote computer
.DESCRIPTION
Enables the rollback policy on a local or remote computer
.PARAMETER Computername
Name of computer or computers to enable the rollback policy
.PARAMETER Passthru
Displays the object after enabling the rollback policy on a computer
.NOTES
Name: Enable-RollbackPolicy
Author: Boe Prox
DateCreated: 06/24/2011
Links:
.EXAMPLE
Enable-RollbackPolicy -Computername DC1 -Passthru
Rollback Computer
-------- --------
Enable DC1
Description
-----------
This enables the rollback policy on DC1 and returns the object showing that the policy has
been enabled.
.EXAMPLE
Enable-RollbackPolicy -Computername DC1,server1,server2 -Passthru
Rollback Computer
-------- --------
Enable DC1
Enable server1
Enable server2
Description
-----------
This enables the rollback policy on DC1,server1 and server2 and returns the object showing that the policy has
been enabled.
#>
[cmdletbinding(
SupportsShouldProcess = 'True'
)]
Param (
[parameter()]
[string[]]$Computername,
[parameter()]
[Switch]$Passthru
)
Process {
ForEach ($computer in $computername) {
Try {
#Make initial registry connection
Write-Verbose "Making registry connection to remote computer"
$registry = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine",$computer)
If ($PScmdlet.ShouldProcess("$computer","Enable Rollback")) {
#Connect to the subkey where the rollback is located
Write-Verbose "Making registry connection to 'Software' key"
If ($registry.GetSubkeyNames() -notcontains "Software") {
$registry.CreateSubKey("Software")
$subkey = $registry.OpenSubKey("Software",$True)
}
Else {
$subkey = $registry.OpenSubKey("Software",$True)
}
Write-Verbose "Making registry connection to 'Policies' key"
If ($subkey.GetSubKeyNames() -notcontains "Policies") {
$subkey.CreateSubKey("Policies")
$subkey = $subkey.OpenSubKey("Policies",$True)
}
Else {
$subkey = $subkey.OpenSubKey("Policies",$True)
}
Write-Verbose "Making registry connection to 'Microsoft' key"
If ($subkey.GetSubKeyNames() -notcontains "Microsoft") {
$subkey.CreateSubKey("Microsoft")
$subkey = $subkey.OpenSubKey("Microsoft",$True)
}
Else {
$subkey = $subkey.OpenSubKey("Microsoft",$True)
}
Write-Verbose "Making registry connection to 'Windows' key"
If ($subkey.GetSubKeyNames() -notcontains "Windows") {
$subkey.CreateSubKey("Windows")
$subkey = $subkey.OpenSubKey("Windows",$True)
}
Else {
$subkey = $subkey.OpenSubKey("Windows",$True)
}
Write-Verbose "Making registry connection to 'Installer' key"
If ($subkey.GetSubKeyNames() -notcontains "Installer") {
$subkey.CreateSubKey("Installer")
$subkey = $subkey.OpenSubKey("Installer",$True)
}
Else {
$subkey = $subkey.OpenSubKey("Installer",$True)
}
Write-Verbose "Checking to see if 'DisableRollback' value name exists"
If ($subkey.GetValueNames() -notcontains "DisableRollback") {
Write-Verbose "Creating DisableRollback value name and setting to 0 (Enable)"
$subkey.SetValue("DisableRollback","0","DWord")
}
Else {
Write-Verbose "Enabling Rollback"
$subkey.SetValue("DisableRollback","0","DWord")
}
If ($PSBoundParameters['Passthru']) {
Write-Verbose "Retrieving value of 'DisableRollback'"
$value = $subkey.GetValue('DisableRollback')
Switch ($value) {
0 {$hash = @{Computer = $computer;Rollback = "Enabled"}}
1 {$hash = @{Computer = $computer;Rollback = "Disabled"}}
Default {$hash = @{Computer = $computer;Rollback = "Unknown"}}
}
}
}
}
Catch {
Write-Warning "$($computer): $($Error[0])"
}
Finally {
If ($PSBoundParameters['Passthru']) {
$object = New-Object PSObject -Property $hash
$object.PSTypeNames.Insert(0,'RollbackStatus')
Write-Output $object
}
}
}
}
}
Export-ModuleMember -Function Get-RollbackPolicy,Enable-RollbackPolicy,Disable-RollbackPolicy

Pingback: Registry rollback | Zukklish