ManageRoles.psm1

<# ManageRoles.psm1
 
    This powershell module allows you to manage a role's actions and scope buy outputting them to editable text files.
    Roles are AD objects that span multiple subscriptions, however they have a history of being affliated with a
    subscription that you created them from. To manage them you must be connected to a subscription that is in
    the list of subscriptions for that role (known as assignablescope).
 
    The Commandlets names are structured as follows
    <verb>-Role<object>
    <verb> = Get - retrieves role infomation and writes to a file
               Set - Overwrites a roles information from a file
               New - Creates a new role
               Remove - Removes a role, Note: will remove it from all subscriptions
    <object> = blank (Applies to Actions and Scope or whole object)
               Actions - list of actions (aka Provider Operations)
               Scope - list of subscription ids
 
    Actual functions
    Set-RoleFilename - Internal function to remove '<prefix>]' from role to create a filename substring
    Get-Role - Outputs a role's actions to an action file and scope to a scope file.
    Get-RoleActions - Outputs a role's actions to an action file 'actions-*.txt' and scope to a scope file.
    Get-RoleScope - Outputs a role's scope to an scope file 'scope-*.txt' and scope to a scope file.
    Set-Role - Overwrites a role based on an actions file and a scope file.
    Set-RoleActions - Overwrites a role's actions to an action file 'actions-*.txt' and scope to a scope file.
    Set-RoleScope - Overwrites a role's scope to an scope file 'scope-*.txt' and scope to a scope file.
    New-Role - Creates a new role based on an actions file and a scope file
    Remove-Role - Removes a role from active directory from all subscriptions (scope)
    Edit-RoleActions - Gets a role's actions, opens up notepad on the actions file, then overwrites the actions from that file
 
Notes:
In PowerShell, use the Get-AzProviderOperation command to get the list of operations
for the Microsoft.Support resource provider you are interested in. It's helpful to know the operations
that are available to you when creating custom roles.
 
Example: Get-AzProviderOperation "Microsoft.Support/*" | Format-Table Operation, Description -AutoSize
 
Custom roles are stored in Active Directory but they are accessed via the subscriptions in the assignable scope.
Your current logged in context must be in the assignablescope for these operations to work.
To see your current context use the following command Write-output "Connected to $($(Get-azcontext).name)"
To set your context use the command Connect-AzAccount -Subscription -Tenant
 
################################################################################################################
#>

function Set-RoleFilename {
<#
.Synopsis
Remove the "<prefix>]" string from filename
 
.Description
The braces '[]' are not valid filename characters so we need to remove them.
This will find the closing brace (right angle bracket) and trim off the string up to it.
 
.Parameter role
Filename - full path or not doesn't matter
#>

Param ( [string] $role )
$loc=$role.LastIndexOf("]") + 1
$len=$role.Length - $loc
$filename=$role.Substring($loc,$len).Trim()
Return $filename
}

function Get-Role {
<#
.Synopsis
Output a role's actions and scopes to two text files.
 
.Description
This function calls Get-RoleActions and Get-RoleScope;
It creates two files scope-<role>.txt and actions-<role>.txt
One subscription or action per line.
 
.Parameter Role
Required - name of the role you want to get the scope from
If not specified it will look for a global variable $global:scope
 
#>

Param ( [string] $role )

If ($role -eq "") {$role = $global:role}
If ($role -eq "") {write-output "Please supply -role parameter"; Return}
$global:role = $role;

$roledef = Get-RoleActions;
$roledef = Get-RoleScope;
}


function Get-RoleActions {
<#
.Synopsis
Output a role's actions to a text file.
 
.Description
This function will output the permissions (actions) to a text file which you may then
edit and use the New-Role or Set-Role function to create a new role or set an existing role.
It creates the file actions-<role>.txt one action per line.
 
.Parameter Role
Name of the role you want to get the actions from.
If not specified this will look for a global variable $global:role to use.
Your current subscription context must have the role in its scope for this to work.
Enter $(Get-azcontext).name at the powershell prompt to see your current subscription context.
 
#>

Param ( [string] $role )

If ($role -eq "") {$role = $global:role}
If ($role -eq "") {write-output "Please supply -role parameter"; Return;}
$global:role = $role;

$actionsfile = "Actions-" + $(Set-rolefilename -filename $role) + ".txt";

# Get the current role definition
Try   { $roledef = Get-AzRoleDefinition -Name $role -ErrorAction Stop; }
Catch { Write-output "Unable to get $role, error $($_.Exception.Message)"; Return}

Try   { If ($roledef.Name = "") { Write-output "Unable to get $role from subscription $(Get-AzContext).name"; Return;} }
Catch { 
    Write-output "Unable to get $role from subscription $($(Get-AzContext).name)";
    Write-Output "Error $($_.Exception.Message)"; 
    Write-Output "Try setting your context to another subscription. Connect-AzAccount -Subscription $subid"; 
    Return;
}

# write the actions to the file
Set-Content -Path $actionsfile -Value $roledef.Actions

Write-Host "$role actions have been written to $actionsfile"
}

function Get-RoleScope {
<#
.Synopsis
Output a role's scopes to a text file.
 
.Description
This function will output the scopes (subscriptions or resource groups) to a text file
in the format scope-<role>.txt
 
The text file will have one subscription per line in the format
            /subscriptions/<subscription-id>
example: /subscriptions/a1234567-890a-bcde-f012-34567890abcd
 
.Parameter Role
Name of the role you want to get the actions from.
If not specified this will look for a global variable $global:role to use.
Your current subscription context must have the role in its scope for this to work.
Enter $(Get-azcontext).name at the powershell prompt to see your current subscription context.
     
#>

Param ( [string] $role )

If ($role -eq "") {$role = $global:role}
If ($role -eq "") {write-output "Please supply -role parameter"; Return;}
$global:role = $role;

$scopefile = "Scope-" + $(Set-rolefilename -role $role) + ".txt";

# Get the current role definitoin
$roledef = Get-AzRoleDefinition -Name $role

# write the scopes to the file
Set-Content -Path $scopefile -Value $roledef.AssignableScopes

Write-Host "$role scopes have been written to $scopefile"
}

function Set-Role {
<#
.Synopsis
Overwrites a role's actions and scope
 
.Description
Sets the role's actions to the list from an actions file.
Sets the role's scope to the list from the scope file.
For more in-depth documentation on custom roles check out https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal
 
.Parameter Role
Name of the role you want to get the actions from.
If not specified this will look for a global variable $global:role to use.
Your current subscription context must have the role in its scope for this to work.
Enter $(Get-azcontext).name at the powershell prompt to see your current subscription context.
 
.Parameter actionsfile
Text file with list of permissions "actions".
Defaults to actions-<role>.txt if not supplied.
Use Get-RoleActions to dump out an existing roles actions to a text file for editting.
One action per line in the format
            {Company}.{ProviderName}/{resourceType}/{action}
example: Microsoft.Compute/hostgroups/*
 
To see a full list of ARM resource provider operations use the PowerShell command Get-AzProviderOperation
To see a full list of "Azure Resource Manager resource provider operations" go to https://docs.microsoft.com/en-us/azure/role-based-access-control/resource-provider-operations
 
.Parameter scopefile
File with list of subscriptions in the format /subscription/<guid>
Defaults to scope-<role>.txt if not supplied.
The text file will have one subscription per line in the format
            /subscriptions/<subscription-id>
example: /subscriptions/a1234567-890a-bcde-f012-34567890abcd
 
#>


Param ( [string] $role, 
        [string] $actionsfile,
        [string] $scopefile
)

# validate parameters
If ($role -eq "") {$role = $global:role}
If ($role -eq "") {write-output "Please supply -role parameter"; Return;}
$global:role = $role;

If ($actionsfile -eq "") {$actionsfile = "Actions-" + $(Set-rolefilename -role $role) + ".txt";}
If ($scopefile   -eq "") {$scopefile   = "Scope-"   + $(Set-rolefilename -role $role) + ".txt";}

# Get the current role definition and overwrite the actions and scope
$roledef                    = Get-AzRoleDefinition -Name $rolename;
$roledef.Actions            = Get-Content -Path $actionsfile;
$roledef.AssignableScopes   = Get-Content -Path $scopefile;
$roledef                    = Set-AzRoleDefinition -Role $roledef;

Write-output "$rolename [Id= $($roledef.Id) ] updated"
Return $roledef
}

function Set-RoleActions {
<#
.Synopsis
Overwrites a role's actions
 
.Description
Sets the role actions to the list from an actions text file.
Use Get-RoleActions to dump out an existing role's actions to a text file for editting.
 
.Parameter Role
Name of the role you want to get the actions from.
If not specified this will look for a global variable $global:role to use.
Your current subscription context must have the role in its scope for this to work.
Enter $(Get-azcontext).name at the powershell prompt to see your current subscription context.
 
.Parameter actionsfile
Text file with list of permissions "actions".
Defaults to actions-<role>.txt if not supplied.
Use Get-RoleActions to dump out an existing roles actions to a text file for editting.
One action per line in the format
            {Company}.{ProviderName}/{resourceType}/{action}
example: Microsoft.Compute/hostgroups/*
 
To see a full list of ARM resource provider operations use the PowerShell command Get-AzProviderOperation
To see a full list of "Azure Resource Manager resource provider operations" go to https://docs.microsoft.com/en-us/azure/role-based-access-control/resource-provider-operations
 
#>


Param ( [string] $role, 
        [string] $actionsfile
)

# Validate parameters
If ($role -eq "") {$role = $global:role}
If ($role -eq "") {write-output "Please supply -role parameter"; Return;}
$global:role = $role;
If ($actionsfile -eq "") {$actionsfile = "Actions-" + $(Set-rolefilename -role $role) + ".txt";}

# Get the current role definition
try {$roledef = Get-AzRoleDefinition -Name $role -ErrorAction Stop }
catch {write-output "Error retrieving role $role"; Return;}

$roledef.Actions = Get-Content -Path $actionsfile;
$roledef         = Set-AzRoleDefinition -Role $roledef

Write-output "$role [Id= $($roledef.Id) ] actions updated with $($roledef.Actions.Count) actions"
}

function Set-RoleScope {
<#
.Synopsis
Sets a role's scope list
 
.Description
Overwrite an existing role's AssignableScope entered in a text file.
Use Get-RoleScope to dump out an existing role's scope to a text file for editting.
 
.Parameter Role
Name of the role you want to get the actions from.
If not specified this will look for a global variable $global:role to use.
Your current subscription context must have the role in its scope for this to work.
Enter $(Get-azcontext).name at the powershell prompt to see your current subscription context.
 
.Parameter scopefile
File with list of subscriptions in the format /subscription/<guid>
Defaults to Scope-<role>.txt if not supplied.
The text file will have one subscription per line in the format
            /subscriptions/<subscription-id>
example: /subscriptions/a1234567-890a-bcde-f012-34567890abcd
 
#>

Param ( [Parameter(Mandatory=$true)]  [string] $role, 
        [Parameter(Mandatory=$false)] [string] $scopefile )

# Validate $role parameter
If ($role -eq "") {$role = $global:role};
$global:role = $role;
If ($role -eq "") {Write-Output "Please pass -role parameter"; exit 1};
If ($scopefile -eq "") {$scopefile = "Scope-" + $(Set-rolefilename -role $role) + ".txt";}

try {$roledef = Get-AzRoleDefinition -Name $role -ErrorAction Stop}
catch {write-output "Error retrieving role $role"; Return;}

$roledef.AssignableScopes = Get-Content -Path $scopefile
$roledef = Set-AzRoleDefinition -Role $roledef

Write-output "$role [Id= $($roledef.Id) ] = AssignableScope updated"
Write-output "$role [Id= $($roledef.Id) ] AssignableScopes updated with $($roledef.AssignableScopes.Count) scopes"
}

function New-Role {
<#
.Synopsis
Create a new custom role
 
.Description
Create a new role based on Azure actions entered in a text file.
Use Get-RoleActions to dump out an existing role's actions to a text file for editting.
Use Get-RoleScope to dump out an existing role's scope to a text file for editting.
For more in-depth documentation on custom role's check out https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal
 
.Parameter Role
Name of the role you want to create
 
.Parameter actionsfile
Text file with list of permissions "actions".
If not specified this will create a default action of */read
 
Use Get-RoleActions to dump out an existing roles actions to a text file for editting.
One action per line in the format
            {Company}.{ProviderName}/{resourceType}/{action}
example: Microsoft.Compute/hostgroups/*
To see a full list of "Azure Resource Manager resource provider operations" go to https://docs.microsoft.com/en-us/azure/role-based-access-control/resource-provider-operations
To see a full list of ARM resource provider operations use the PowerShell command Get-AzProviderOperation
Example: Get-AzProviderOperation "Microsoft.Compute/*" | Format-Table Operation, Description -AutoSize
 
.Parameter scopefile
Text file with list of subscriptions.
If not specified this will attempt to set the scope to the contents of a file named Scope-test.txt
 
Use Get-RoleScope to dump out an existing role's scope to a text file for editting.
File with list of subscriptions in the format /subscription/<guid>
Defaults to scope-<role>.txt if not supplied.
The text file will have one subscription per line in the format
            /subscriptions/<subscription-id>
example: /subscriptions/a1234567-890a-bcde-f012-34567890abcd
#>


Param ( [string] $role, 
        [string] $actionsfile,
        [string] $scopefile 
)

# Validate $role parameter
If ($role -eq "") {Write-Output "Please pass -role parameter with the name of the role"; exit 1}
$global:role = $role;

# Validate $actionsfile
If ($actionsfile -eq "") {
    $actionsfile = "Actions-" + $(Set-rolefilename -role $role) + ".txt";
    Set-Content -Path $actionsfile "*/read";
    Write-Output "No actions specified. Setting actions to */read"
}

# Validate $scopefile (default = Scope-test.txt) else Error
If ($scopefile -eq "") {$scopefile = "Scope-" + $(Set-rolefilename -role $role) + ".txt";}
Try   { get-item -path $scopefile -ErrorAction Stop }
Catch {
    Try   { Copy-Item -Path '.\Scope-test.txt' -Destination $scopefile -ErrorAction Stop; 
            Write-Output "No scope specified. Setting scope to Scope-test.txt"
    }
    Catch {
        Write-Output "Error - default scope file Scope-test.txt not found.";
        Write-Output "Use Get-RoleScope -role 'your role name' to create an example scopefile"; 
        Return;
    }
}

# Create a new role definition object
$global:role = $role;
$roledef = New-Object -type Microsoft.Azure.Commands.Resources.Models.Authorization.PSRoleDefinition;
$roledef.Id          = $null
$roledef.IsCustom    = $true
$roledef.Name        = $role
$roledef.Description = $role
$roledef.Actions          = Get-Content -Path $actionsfile;
$roledef.AssignableScopes = Get-Content -Path $scopefile;

Try {
    $roledef = New-AzRoleDefinition -Role $roledef -ErrorAction Stop;
    Write-output "$role [Id= $($roledef.Id) ] created";
    Write-Output "Please allow 15min for Active Directory to propgate this change.";
    Write-Output "If you are logged into the Azure Portal you may need to relogin.";
}
Catch {
    Write-output "Error creating $role";
    Write-Output "Message: $($_.Exception.Message)"; 
}

}

function Remove-Role {
<#
.Synopsis
Remove a role from Active Directory.
 
.Description
Removes a role which will remove it from all subscriptions.
This is not recoverable. Recommend using Get-Role so you have a copy
of the actions and scope as a backup.
Note: Custom roles are stored in Active Directory and may take time to propgate changes
 
.Parameter Role
Name for the Role to remove.
Note: Defaults to global variable $global:role
 
.Example
    Remove-Role
 
.Example
    Remove-Role -role "I dislike this custom role"
 
#>


Param ( [string] $role )

# Validate $role parameter
If ($role -eq "") {$role = $global:role}
$global:role = $role;
If ($role -eq "") {Write-Output "Please pass -role parameter"; Return;}

# Get the current role definition
Try { 
    Remove-AzRoleDefinition -Name $role -ErrorAction Stop;
    Write-output "Deleted role $role"
    Write-Output "Please allow 15min for Active Directory to propgate this change"
}
Catch { 
    write-output "Failed to delete role $role";
    write-output $_.Exception.Message;
    Return;
}

}


function Get-RoleHelp {
Write-Output ""
Write-Output "*************** Welcome to ManageRoles Powershell Module ***************"
Write-Output "You are connected to $($(Get-azcontext).name)"
Write-Output ""
Write-Output "ManageRoles installs the following PowerShell functions for you:"
Write-Output "**************************************************************************************************************************"
Write-Output "Get-Role - Writes a role's actions and scope to seperate files."
Write-Output "Get-RoleActions - Writes a role's actions to an action file named actions-<role>.txt "
Write-Output "Get-RoleScope - Writes a role's scope to a scope file named scope-<role>.txt"
Write-Output "Set-Role - Overwrites a role based on an actions file and a scope file."
Write-Output "Set-RoleActions - Overwrites a roles actions to an action file actions-*.txt and scope to a scope file."
Write-Output "Set-RoleScope - Overwrites a roles scope to an scope file scope-*.txt and scope to a scope file."
Write-Output "New-Role - Creates a new role based on an actions file and a scope file"
Write-Output "Remove-Role - Removes a role from active directory from all subscriptions (scope)"
#Write-Output "Edit-RoleActions - Gets a roles actions, opens up notepad on the actions file, then overwrites the actions from that file"
Write-Output ""
Write-Output "Manage-Roles examples: "
Write-Output " Get-Help *-role*"
Write-Output " New-Role -role 'My new role'"
Write-Output " Get-Role -role 'Cosmos DB'"
Write-Output " Set-RoleActions -role 'Cosmos DB'"
Write-Output " Set-RoleScope -role 'Cosmos DB' -scopefile Scope-allsubs.txt"
Write-Output " New-Role -role 'Cosmos DB v2' -actionsfile Actions-CosmosDB.txt -scopefile Scope-testsubs.txt"
Write-Output ""
Write-Output "For more in-depth documentation on custom roles check out https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal"
Write-Output ""
Write-Output "To see a full list of Azure Resource Manager resource provider operations"
Write-Output "go to https://docs.microsoft.com/en-us/azure/role-based-access-control/resource-provider-operations"
Write-Output "To see a full list of ARM resource provider operations use the PowerShell command Get-AzProviderOperation"
Write-Output ""
}

##############################################################################
# Main Program
##############################################################################
# set aliases
Try   {Get-Alias -name gr   -ErrorAction Stop;
       Set-Alias -Name gr   -Value Get-Role -Scope Global}
Catch {New-Alias -Name gr   -Value Get-Role -Scope Global}

Try   {Get-Alias -name gra  -ErrorAction Stop;
       Set-Alias -Name gra  -Value Get-RoleActions -Scope Global}
Catch {New-Alias -Name gra  -Value Get-RoleActions -Scope Global}

Try   {Get-Alias -name grs  -ErrorAction Stop;
       Set-Alias -Name grs  -Value Get-RoleScope -Scope Global}
Catch {New-Alias -Name grs  -Value Get-RoleScope -Scope Global}

Try   {Get-Alias -name sra  -ErrorAction Stop;
       Set-Alias -Name sra  -Value Set-RoleActions -Scope Global}
Catch {New-Alias -Name sra  -Value Set-RoleActions -Scope Global}

Try   {Get-Alias -name srs  -ErrorAction Stop;
       Set-Alias -Name srs  -Value Set-RoleScope -Scope Global}
Catch {New-Alias -Name srs  -Value Set-RoleScope -Scope Global}

Try   {Get-Alias -name rr   -ErrorAction Stop;
       Set-Alias -Name rr   -Value Remove-Role -Scope Global}
Catch {New-Alias -Name rr   -Value Remove-Role -Scope Global}

Try   {Get-Alias -name grh  -ErrorAction Stop;
       Set-Alias -Name grh  -Value Get-RoleHelp -Scope Global}
Catch {New-Alias -Name grh  -Value Get-RoleHelp -Scope Global}