Working with Software Rollback Policies using PowerShell

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()

image

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')

image

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')

image

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

image

Enable-RollbackPolicy -Computername DC1 -Passthru

image

Disable-RollbackPolicy -Computername DC1 -Passthru

image

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

PoshCode

Script Repository

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

About Boe Prox

Microsoft Cloud and Datacenter MVP working as a SQL DBA.
This entry was posted in Modules, powershell and tagged , , , . Bookmark the permalink.

One Response to Working with Software Rollback Policies using PowerShell

  1. Pingback: Registry rollback | Zukklish

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s