Please note that this script performs only a part of the necessary operations. If protection schedule and retention policies are to be transfered please use KB#157147 (https://support.quest.com/appassure/kb/157147)
A powershell script for Powershell 3.0 or later has been prepared. The script needs to be run first on the Target core (the one that will become the Source). It reads the list of agents and shows them in a gridview which allows inspecting the properties of each agent, filtering them based a series of parameters and choosing the ones to be transformed into protected servers.
Once the agents are selected, the user name and password necessary to protect the agents are requested.
WARNING!: Please select only replicated agents that can be protected with the same credentials. Using mismatched credentials will result in REMOVING the odd agent from replication without protecting it.
The agent information is extracted straight from the Registry and as such allowing the script to be applied to very large environments that are prone to timeouts when collecting agent data. Same goes for suspening the snapshots for agents newly added to protection, operation that allows modifying the backup schedule and retention policy at agent level.
The second step is running the script with the -removeprotectedservers parameter on the core that used to be the source.
This parameter tells the script to remove the deleted replications specific to the chosen agents and removing them from protection.
The script source code is shown below between horizontal lines and is attached to the the KB.
________________________________________
param ($logpath="$env:userprofile\Downloads\Log-$([GUID]::NewGuid()).txt",[switch]$removeprotectedservers)
function get-agents{
param ($r=$repohash)
$agents=@()
$agentkey = "HKEY_LOCAL_MACHINE\SOFTWARE\AppRecovery\Core\Agents"
$agentsobject = get-childitem -Path "Registry::$($agentkey)"
$targethashtable = get-targethash
foreach($agentobject in $agentsobject){
$fullpath = $agentobject.name
$id = $fullpath | Split-Path -Leaf
$agentname = (get-itemproperty -path "Registry::$($agentobject.name)" -name displayname).displayname
$testpath="Registry::$($fullpath)\ReplicationService\RemoteCoreSummaryInfo"
$test = test-path -Path $testpath
if($test){$mastercorename=(Get-ItemProperty -Path $testpath -Name hostname).hostname;$mastercoreid=(Get-ItemProperty -Path $testpath -Name id).id;$agenttype="Replicated"}else{$mastercorename=$null;$agenttype="Protected"}
if($targethashtable){
$testpath2 = "Registry::$($fullpath)\ReplicationService\AgentRemoteSlaveCoreSettings\0"
$test2 = test-path -Path $testpath2
$targetcorename=$null; $targetcoreid=$null
if($test2){$targetcoreid=(Get-ItemProperty -Path $testpath2 -Name "RemoteSlaveCoreId").RemoteSlaveCoreId;$targetcorename=$targethashtable.$targetcoreid}
}
$repoid = (Get-ItemProperty -Path "Registry::$($fullpath)\AgentProtectionConfiguration\StorageConfiguration" -Name RepositoryId).repositoryid
$agent=[pscustomobject]@{Agentname=$agentname;Agenttype=$agenttype;Agentid=$id;MasterCoreName=$mastercorename;Volumes=(get-protecteddrives -id ($id));Repository=$r.item($repoid);MasterCoreId=$mastercoreid;TargetCoreName=$targetcorename;Targetcoreid=$targetcoreid}
$agents+= $agent
}
return $agents | sort-object agentname
}
function get-repositories {
$repohash=@{}
$repositorykey = "HKEY_LOCAL_MACHINE\SOFTWARE\AppRecovery\Core\Repositories"
[array]$repositoryfullids = (get-childitem -Path "Registry::$($repositorykey)").name
foreach($repositoryfullid in $repositoryfullids){
$repoid = $repositoryfullid | Split-Path -Leaf
$reponame= (Get-ItemProperty -Path "Registry::$($repositoryfullid)\Specification" -Name name).name
$repohash.Add($repoid,$reponame)
}
return $repohash
}
function get-protecteddrives {
param ($id)
$vollist = @()
try{
$agprotectiongroups = (get-childitem -path "REGISTRY::HKEY_LOCAL_MACHINE\SOFTWARE\AppRecovery\Core\Agents\$id\AgentProtectionConfiguration\ProtectionGroups" -ErrorAction Stop).name
foreach($agprotectiongroup in $agprotectiongroups){
$agvolumespath ="REGISTRY::$agprotectiongroup\VolumeNames"
$agvolumes = (get-childitem -path $agvolumespath).Name
foreach ($agvolume in $agvolumes){
try{
$agvol = (Get-ItemProperty -Path "REGISTRY::$agvolume" -Name Displayname -ErrorAction Stop).displayname
}
catch {
$agvol = (Get-ItemProperty -Path "REGISTRY::$agvolume" -Name Guidname).Guidname
$agvol = "(Volume '" + $agvol + "')"
}
$vollist += $agvol
}
}
return ($vollist | sort-object)
}
catch{return $null}
}
function get-coreid{
$idpath = "HKEY_LOCAL_MACHINE\SOFTWARE\AppRecovery\Core\CoreId"
return (get-itemproperty -Path "Registry::$($idpath)" -Name id).id
}
function process-volumes{
param([array]$rawvols)
$vols=@()
foreach ($vol in $rawvols){
$vols += $vol.replace("\","")
}
return $vols
}
function get-targethash{
$targetpath="Registry::HKEY_LOCAL_MACHINE\SOFTWARE\AppRecovery\Core\Replication\RemoteCores\Slaves"
[array]$targetcores = get-childitem $targetpath
if($targetcores.Count -le 0){return $null}
$targethash=@{}
foreach($targetcore in $targetcores){
$targetcoreidx = (get-itemproperty -Path "Registry::$($targetcore.name)" -name id).id
$targetcorenamex = (get-itemproperty -Path "Registry::$($targetcore.name)" -name hostname).hostname
$targethash.Add($targetcoreidx,$targetcorenamex)
}
return $targethash
}
function pause-agent{
param ($agentid)
if(!(Test-Path "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\AppRecovery\Core\Agents\$($agentid)")){return }
$regroot="HKEY_LOCAL_MACHINE\SOFTWARE\AppRecovery\Core\Agents\$($agentid)\AgentProtectionConfiguration"
$default = "Replay.Core.Contracts.Transfer.ProtectionPauseConfiguration"
$pausets = ((get-date).ToUniversalTime()).tostring("yyyy-MM-ddTHH:mm:ssZ")
$pauseinterval="00:00:00"
$PauseType=1
if(Test-Path -Path "Registry::$regroot\PauseConfiguration"){Write-Host "already paused. Updating with current values";Remove-Item -Path "Registry::$regroot\PauseConfiguration" -Force }
if(!(Test-Path -Path "Registry::$regroot\PauseConfiguration")){
new-item -Path "Registry::$regroot\PauseConfiguration" -Force | Out-Null
set-item -Path "Registry::$regroot\PauseConfiguration" -Value $default | Out-Null
new-itemproperty -Path "Registry::$regroot\PauseConfiguration" -name "PausedTimeStamp" -Value $pausets -PropertyType "String" | Out-Null
new-itemproperty -Path "Registry::$regroot\PauseConfiguration" -name "PauseInterval" -Value $pauseinterval -PropertyType "String" | Out-Null
new-itemproperty -Path "Registry::$regroot\PauseConfiguration" -name "PauseType" -Value $PauseType -PropertyType "DWord" | Out-Null
}
}
cls
Write-Host "Switch Target-To-Source Helper`n------------------------------`n" -f Green
Write-Host "`nWARNING!"
Write-Host "Making a copy of the HKEY_LOCAL_MACHINE\SOFTWARE\AppRecovery\Core key before proceeding!"
reg.exe export HKEY_LOCAL_MACHINE\SOFTWARE\AppRecovery\Core "$env:userprofile\Downloads\CoreRegistryCopy-$([GUID]::NewGuid()).reg"
Write-Host "Done!`n"
$repohash = get-repositories
$agentsraw = get-agents -r $repohash #(get-infoobject -uriend "agents").agents.agent
$agents= $agentsraw | select-object @{n="displayname";e={$_.agentname}},agenttype,@{n="id";e={$_.agentid}},repository,mastercorename,MasterCoreId,TargetCoreName,TargetCoreId,@{n="Volumes";e={process-volumes -rawvols $_.volumes}}
$agents = $agents | Out-GridView -Title "Select the agents to process. $addendum" -PassThru
if(!($agents)){Write-Host "`n`nExiting...`n`n";exit}
if($removeprotectedservers.IsPresent){
[array]$protectedagents = $agents | where {$_.agenttype -like "*protected*"}
Out-File -InputObject "Protected Agents Removal while keeping RPs log as of $(get-date)" -FilePath $logpath -Width 150
Out-File -InputObject ($protectedagents | ft -AutoSize| out-string) -FilePath $logpath -Append -Width 150
$body2 = @"
<?xml version="1.0" ?>
<deleteAgentRequest xmlns="http://apprecovery.com/management/api/2010/05">
<DeleteRecoveryPoints>false</DeleteRecoveryPoints>
<DisableAgentVolumes>true</DisableAgentVolumes>
</deleteAgentRequest>
"@
foreach($protectedagent in $protectedagents){
try{
if($protectedagent.targetcoreid){
Write-Host "Removing Replication for Protected Agent $($protectedagent.displayname) from core $($protectedagent.TargetCoreName)"
Out-File -InputObject "Removing Replication for Protected Agent $($protectedagent.displayname) from core $($protectedagent.TargetCoreName)" -FilePath $logpath -Append -Width 150
$uri2 = "https://$($env:computername):8006/apprecovery/api/core/replication/slaves/$($protectedagent.targetcoreid)/replicatedagents/$($protectedagent.Id)"
Invoke-RestMethod -Uri $uri2 -Method Delete -UseDefaultCredentials
}
Write-Host "Removing Protected Agent $($protectedagent.displayname) while keeping the recovery points"
Out-File -InputObject "Removing Protected Agent $($protectedagent.displayname) while keeping the recovery points" -FilePath $logpath -Append -Width 150
Invoke-RestMethod -Uri "https://$($env:computername):8006/apprecovery/api/core/agents/$($protectedagent.Id)/delete" -Method POST -Body $body2 -UseDefaultCredentials -ContentType "application/xml" -ErrorAction stop
}
catch {Write-Host "`nFailed operation on agent $($protectedagent.displayname)`n" -ForegroundColor Red
Out-File -InputObject "Failed operation on agent $($protectedagent.displayname)" -FilePath $logpath -Append -Width 150
}
}
}
else{
#------ CREDENTIALS ------------------------
Write-Host "Credentials used to protect agents are needed. It is important to get them right in order to avoid complications`n" -f green
$username = Read-Host "enter username `[domain\user`]"
do{
$passwordx = Read-Host "enter password" -AsSecureString
$tempstringx = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR( $passwordx))
$passwordy= Read-Host "re-enter password" -AsSecureString
$tempstringy = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR( $passwordy))
if(($tempstringx -ne $tempstringy)){Write-Host "passwords do not match! Try again."}
}until($tempstringx -eq $tempstringy)
$cred = New-Object System.Management.Automation.PSCredential ("username", $passwordy)
$password= $cred.getnetworkcredential().password
#-------
[array]$replicatedagents = $agents | where {$_.agenttype -like "*Replicated*"}
Out-File -InputObject "Replication to protection conversion log as of $(get-date)" -FilePath $logpath -Width 150
Out-File -InputObject ($replicatedagents | ft -AutoSize| out-string) -FilePath $logpath -Append -Width 150
foreach($replicatedagent in $replicatedagents ){
write-Host "Removing Replicated Agent $($replicatedagent.displayname) from replication while keeping the recovery points"
Out-File -InputObject "Removing Replicated Agent $($replicatedagent.displayname) from replication while keeping the recovery points" -FilePath $logpath -Append -Width 150
$uriend = "https://$($env:computername):8006/apprecovery/api/core/replication/masters/$($replicatedagent.mastercoreid)/replicatedagents/$($replicatedagent.Id)/?deleteRecoveryPoints=False"
start-sleep 1
try {
invoke-restmethod -URI $uriend -Method DELETE -usedefaultcredentials -ErrorAction stop
Write-Host "Protecting agent $($replicatedagent.displayname)"
Out-File -InputObject "Protecting agent $($replicatedagent.displayname)" -FilePath $logpath -Append -Width 150
start-protect -Repository "$($replicatedagent.Repository)" -AgentName $($replicatedagent.displayname) -AgentPort "8006" -AgentUserName $username -agentpassword $password -volumes all
Out-File -InputObject "Suspending snapshots for newly protected agent agent $($replicatedagent.displayname)" -FilePath $logpath -Append -Width 150
pause-agent -agentid "$($replicatedagent.id)"
Out-File -InputObject "Stopping jobs for newly protected agent agent $($replicatedagent.displayname)" -FilePath $logpath -Append -Width 150
}
catch {Write-Host "`nFailed operation on agent $($replicatedagent.displayname)`n" -ForegroundColor Red
Out-File -InputObject "Failed operation on agent $($replicatedagent.displayname)" -FilePath $logpath -Append -Width 150
}
}
}
Invoke-Item -Path $logpath
________________________________________