I have a customer who has many management groups and wants to synchronize the user roles between them.  There is no easy way to do this in the UI, but it can be done via PowerShell.  One of my co-workers, Jonathan Almquist, blogged about this some time ago.  His script allows one to export the user roles, along with any associated user accounts, and import them in another management group.  It, however, doesn't export any associated Group Scopes, Tasks, or Views.  The script I have created exports everything associated with the user role and is a great example of creating generic types in PowerShell.

The script takes two parameters.  The first is the name of the MS you want to connect to and the second is either "export" or "import".  The export task creates a file called userroles.xml in the same directory as the PS script ran from.  To use the "import" task you must place the userroles.xml in the same directory if it isn't already there.  The script then looks for any user roles in the userroles.xml that don't exist and creates them.  If the user role does exist, but is missing certain users, group scopes, views, or tasks then it will add them.

I have pasted the script below and also attached a copy to the blog.

#FileName: UserRoleExporter.ps1
#Created by: Russ Slaten (
http://blogs.msdn.com/rslaten)
#Created on: 07/24/2008
#Modified by: xxx
#Modified on: xx/xx/xxxx

#===========================FUNCTIONS BEGIN===========================

function Main
{
    #Constants
    $MSPARAM = "MS"
    $TASKPARAM = "TASK"
    $EXPORT = "EXPORT"
    $IMPORT = "IMPORT"
    $XMLFILE = "userroles.xml"
    #Get each of the command line parameters passed by the caller
    $managementServer = GetParameter $MSParam
    $task = GetParameter $TASKPARAM
    Write-Host "Task =" $task
    Write-Host "Management Server =" $managementServer
    #Configure OpsMgr powershell environment
    ValidateSnapIn
    $drive = SetDrive
    if (!$drive){throw("Error configuring OpsMgr Environment")}
    #Connect to the Management Server
    $ms = GetManagementServer $managementServer
    if (!$ms){throw("Error connecting to Management Server")}
    #Start the applicable task
    if ($task.ToString() -eq $EXPORT.ToString()) {ExportRoles $XMLFILE} #Export Roles
    elseif ($task.ToString() -eq $IMPORT.toString()) {ImportRoles $XMLFILE} #Import Roles
    trap [Exception]{Write-Error $_.Exception.Message;ShowHelp;exit;}
} #Main

#Exports Roles from Management Group
function ExportRoles([string]$s)
{
    #Get all non-system user roles
    $userroles = get-userRole | where {$_.IsSystem -eq $False}
    #Make sure some custom user roles exist
    if ($userroles.count -eq 0) {Write-host "No custom user roles found";return 0;}
    #Create the XML object
    $doc = New-Object "System.Xml.XmlDocument"
    $doc.LoadXml("<?xml version='1.0' encoding='utf-8'?><UserRoles xmlns:xsi='
http://www.w3.org/2001/XMLSchema-instance' xmlns:xsd='http://www.w3.org/2001/XMLSchema'></UserRoles>")

    #Loop through each user role instance
    foreach ($mo in $userRoles)
    {
        #Add single instance data to XML
        $elem = $doc.CreateElement("UserRole")
        $elem.SetAttribute("Name",$mo.Name)
        $elem.SetAttribute("DisplayName",$mo.DisplayName)
        $elem.SetAttribute("Description",$mo.Description)
        $elem.SetAttribute("Profile",$mo.MonitoringProfile)

        #Add users to XML if any are defined
        if ($mo.Users.count -ne 0)
        {
            foreach ($u in $mo.Users)
            {
                $elem2 = $doc.CreateElement("User")
                $elem2.SetAttribute("UserName",$u.ToString())
                $temp = $elem.AppendChild($elem2)
            }
        }
        #Loop through the groups if the user has explicitly defined group scopes
        if ($mo.scope.MonitoringObjects.count -gt 0)
        {
            foreach ($grp in $mo.scope.MonitoringObjects)
            {
                $elem2 = $doc.CreateElement("Group")
                $elem2.SetAttribute("GroupScope",$grp.ToString())
                $temp = $elem.AppendChild($elem2)
            }
        }
        #Loop through the views if the user has explicitly defined views
        if ($mo.scope.MonitoringViews.count -gt 0)
        {
            foreach ($view in $mo.scope.MonitoringViews)
            {
                $elem2 = $doc.CreateElement("MonitoringView")
                $elem2.SetAttribute("View",$view.First.ToString())
                $elem2.SetAttribute("Bool",$view.Second.ToString())
                $temp = $elem.AppendChild($elem2)
            }
        }
        #Loop through the non-credential tasks if the user has explicitly defined tasks
        if ($mo.scope.NonCredentialMonitoringTasks.count -gt 0)
        {
            foreach ($task in $mo.scope.NonCredentialMonitoringTasks)
            {
                $elem2 = $doc.CreateElement("NonCredentialMonitoringTask")
                $elem2.SetAttribute("Task",$task.First.ToString())
                $elem2.SetAttribute("Bool",$task.Second.ToString())
                $temp = $elem.AppendChild($elem2)
            }
        }
        #Loop through the credential tasks if the user has explicitly defined tasks
        if ($mo.scope.CredentialMonitoringTasks.count -gt 0)
        {
            foreach ($task in $mo.scope.CredentialMonitoringTasks)
            {
                $elem2 = $doc.CreateElement("CredentialMonitoringTask")
                $elem2.SetAttribute("Task",$task.First.ToString())
                $elem2.SetAttribute("Bool",$task.Second.ToString())
                $temp = $elem.AppendChild($elem2)
            }
        }
        #Write this new element to the XML document       
        $temp = $doc.get_ChildNodes().Item(1).AppendChild($elem)
        Write-Host "---" $mo.DisplayName "-> exported"
    }
    #Save XML to a file
    $doc.save((Join-path $SCRIPTPATH $s))
} #ExportRoles

#Imports Roles from XML in Management Group
function ImportRoles([string]$s)
{
    #Get existing Non-System User Roles
    $existingUserRoles = get-userRole | where {$_.IsSystem -eq $False}
    #Open XML file
    $doc = New-Object "System.Xml.XmlDocument"
    $doc.load((Join-Path $SCRIPTPATH $s))
    #Loop through each user role
    $userRoles = $doc.SelectNodes("UserRoles/UserRole")
    foreach ($mo in $userRoles)
    {
        #Check to see if user already exists
        $bFound = $false
        foreach ($u in $existingUserRoles)
        {
            if ($mo.Name -eq $u.Name)
            {
                Write-Host $mo.Name ":Role already exists, adding permissions"
                $bFound = $true
                ReplicateUserRoleRights $mo
            }
        }
        if (!$bFound)
        {
            #Create new role
            CreateNewUserRole $mo
        }
    }
} #ImportRoles

function ReplicateUserRoleRights([System.Object]$xml)
{
    #Get the user
    $obj = get-userRole | where {$_.Name -eq $xml.Name}
    Write-Host $obj.Name ":Adding rights"
    #First add users
    foreach ($xmlConsoleUser in $xml.User)
    {
        $bFound = $false
        foreach ($consoleUser in $obj.Users)
        {
            if ($xmlConsoleUser.UserName -eq $consoleUser)
            {
                $bFound = $true
            }
        }
        if (!$bFound)
        {
            if ($xmlConsoleUser.UserName.length -gt 1)
            {
                $obj.Users.Add($xmlConsoleUser.UserName)
                $obj.Update()
                Write-Host $obj.Name "-User:" $xmlConsoleUser.UserName "->added"
            }
        }
        else
        {
            Write-Host $obj.Name "-User:" $xmlConsoleUser.UserName "->already exists"
        }       
    }

    #Now Add Group Scopes
    foreach ($xmlGroupScope in $xml.Group)
    {
        $bFound = $false
        foreach ($consoleScope in $obj.Scope.MonitoringObjects)
        {
            if ($xmlGroupScope.GroupScope -eq $consoleScope.ToString())
            {
                $bFound = $true
            }
        }
        if (!$bFound)
        {
            $ret = $true
            [string]$sGuid = $xmlGroupScope.GroupScope
            if ($sGuid.length -ne 0)
            {
                $obj.Scope.MonitoringObjects.Add($sGuid)
                $ret = $obj.Update()
                trap [Exception]{continue}
                if (!$ret)
                {
                    Write-Host $obj.Name "-GroupScope:" $xmlGroupScope.GroupScope "->added"
                }
                else
                {
                    Write-Host $obj.Name "-GroupScope:" $xmlGroupScope.GroupScope "->no matching group"
                }
            }
        }
        else
        {
            Write-Host $obj.Name "-GroupScope:" $xmlGroupScope.GroupScope "->already exists"
        }       
    }   
    #Create generic type (used for views and tasks if there are any)
    $genericType = [Type] "Microsoft.EnterpriseManagement.Common.Pair``2"
    $typeParameters = "System.Guid","System.Boolean"
    [type[]] $typedParameters = $typeParameters
    $closedType = $genericType.MakeGenericType($typedParameters)

    #Now Add Views
    foreach ($xmlView in $xml.MonitoringView)
    {
        $bFound = $false
        foreach ($consoleView in $obj.Scope.MonitoringViews)
        {
            if ($xmlView.View.ToString() -eq $consoleView.First.ToString())
            {
                $bFound = $true
            }
        }
        if (!$bFound)
        {
            $ret = $true
            if ($xmlView.View.length -gt 1)
            {
                if ($xmlView.bool -eq $false)
                {
                    $second = $false
                }
                $params = [guid]$xmlView.View,$second
                $pair = [Activator]::CreateInstance($closedType, $params)
                $obj.Scope.MonitoringViews.Add($pair)
                $ret = $obj.Update()
                trap [Exception]{continue}
                if (!$ret)
                {
                    Write-Host $obj.Name "-View:" $xmlView.View "->added"
                }
                else
                {
                    Write-Host $obj.Name "-View:" $xmlView.View "->no matching group"
                }
            }
        }
        else
        {
            Write-Host $obj.Name "-View:" $xmlView.View "->already exists"
        }       
    }
    #Now Add noncredentialmonitoringtasks
    foreach ($xmlNonCred in $xml.NonCredentialMonitoringTask)
    {
        $bFound = $false
        foreach ($consoleNonCred in $obj.Scope.NonCredentialMonitoringTasks)
        {
            if ($xmlNonCred.Task.ToString() -eq $consoleNonCred.First.ToString())
            {
                $bFound = $true
            }
        }
        if (!$bFound)
        {
            $ret = $true
            if ($xmlNonCred.Task.length -gt 1)
            {
                if ($xmlNonCred.bool -eq $false)
                {
                    $second = $false
                }
                $params = [guid]$xmlNonCred.Task,$second
                $pair = [Activator]::CreateInstance($closedType, $params)
                $obj.Scope.NonCredentialMonitoringTasks.Add($pair)
                $ret = $obj.Update()
                trap [Exception]{continue}
                if (!$ret)
                {
                    Write-Host $obj.Name "-NonCredTask:" $xmlNonCred.Task "->added"
                }
                else
                {
                    Write-Host $obj.Name "-NonCredTask:" $xmlNonCred.Task "->no matching group"
                }
            }
        }
        else
        {
            Write-Host $obj.Name "-NonCredTask:" $xmlNonCred.Task "->already exists"
        }       
    }
    #Now Add credentialmonitoringtasks
    foreach ($xmlCred in $xml.CredentialMonitoringTask)
    {
        $bFound = $false
        foreach ($consoleCred in $obj.Scope.CredentialMonitoringTasks)
        {
            if ($xmlCred.Task.ToString() -eq $consoleCred.First.ToString())
            {
                $bFound = $true
            }
        }
        if (!$bFound)
        {
            $ret = $true
            if ($xmlCred.Task.length -gt 1)
            {
                if ($xmlCred.bool -eq $false)
                {
                    $second = $false
                }
                $params = [guid]$xmlCred.Task,$second
                $pair = [Activator]::CreateInstance($closedType, $params)
                $obj.Scope.CredentialMonitoringTasks.Add($pair)
                $ret = $obj.Update()
                trap [Exception]{continue}
                if (!$ret)
                {
                    Write-Host $obj.Name "-CredTask:" $xmlCred.Task "->added"
                }
                else
                {
                    Write-Host $obj.Name "-CredTask:" $xmlCred.Task "->no matching group"
                }
            }
        }
        else
        {
            Write-Host $obj.Name "-CredTask:" $xmlCred.Task "->already exists"
        }       
    }
} #ReplicateUserRoleRights

function CreateNewUserRole([System.Object]$xml)
{   
    #Create a new User Role Object
    $obj = new-object Microsoft.EnterpriseManagement.Monitoring.Security.MonitoringUserRole
    #Populate the common fields for the userrole
    $obj.Name = $xml.Name
    $obj.DisplayName = $xml.DisplayName
    $obj.Description = $xml.Description        
    $profile = $mg.GetMonitoringProfiles() | where {$_.Name -eq $xml.Profile}
    $obj.MonitoringProfile = $profile
    $mg.InsertMonitoringUserRole($obj)
    #Now Replicate the rights associated with this role
    Write-Host $xml.Name ":New user role created"
    ReplicateUserRoleRights $xml
} #CreateNewUserRole

#Validates the parameters passed by the caller
#Pass either "MS" or "TASK" depending on which parameter you want
function GetParameter([string]$s)
{
    #First, make sure the caller passed at least two parameters to the script
    if ($cmdLineArgs.count -ne 2) {throw("Error getting command line parameters")}
    #Now, validate the contents of the parameter
    if ($s -eq "MS"){return $cmdLineArgs[0]}
    elseif ($s -eq "TASK")
    {
        if ($cmdLineArgs[1] -eq "EXPORT"){return "EXPORT"}
        elseif ($cmdLineArgs[1] -eq "IMPORT") {return "IMPORT"}
        else {throw("Error matching 2nd command line parameter")}
    }
    else {throw("Error getting command line parameters")}
} #GetParameters

#This function tests whether the opsmgr snap-in has been added
function ValidateSnapIn
{
    $snapins = PsSnapIn | select-Object name
    $added = $false
    foreach ($o in $snapins)
    {
        if ($o -like "*Microsoft.EnterpriseManagement.OperationsManager.Client*")
        {
            $added = $true
            break
        }
    }
    if (!$added)
    {
        add-PsSnapIn "Microsoft.EnterpriseManagement.OperationsManager.Client"
        write-Host "OpsMgr Snap-in added."
    }
    else
    {
        write-Host "OpsMgr Snap-in already added."
    }
} #ValidateSnapIn

function SetDrive
{
    #Sets location
    set-location "OperationsManagerMonitoring::"

    $drv = psdrive | select-Object name
    $added = $false
    foreach ($d in $drive)
    {
        if ($d -like "*Monitoring*")
        {
            $added = $true
        }
    }
    if (!$added)
    {
        New-PSDrive -Name: Monitoring -PSProvider: OperationsManagerMonitoring -Root: \
        write-Host "Monitoring Drive added."
    }
    else
    {
        write-Host "Monitoring Drive already added."
    }
    return $psdrive
} #SetDrive

function GetManagementServer([string]$s)
{
    New-ManagementGroupConnection -ConnectionString: $s
    cd Monitoring:\$s
    $mg = (get-item .).ManagementGroup
    return $mg
} #GetManagementServer

function ShowHelp
{
    Write-Host "-----UserRoleExporter.ps1 Help-----"
    Write-Host "This is an example script for exporting and importing user roles from OpsMgr"
    Write-Host ""
    Write-Host "UserRoleExporter.ps1 Usage:"
    Write-Host "Parameter 1: <Management Server Name>"
    Write-Host "Parameter 2: import or export"
    Write-Host "Example: UserRoleExporter.ps1 myRMS export"
    Write-Host ""
} #ShowHelp

#===========================FUNCTIONS END===========================

#Get CmdLine Args and set global
$CmdLineArgs = $Args

#Get path script was called from and set global
$SCRIPTPATH = $MyInvocation.Mycommand.Path | Split-Path -Parent

#Get Management Group Connection
$mg = (get-item .).ManagementGroup

#Calls the main program
Main

# End of Script