I have written the following function in Powershell. It prints the details about a role and its composition. It is not complete yet. I still want to add the metadata attributes for the role, access profiles, and entitlements, the access request configuration, and the rules for the role. Also, with the new enhancement coming where multiple owners can be defined, an update will need to happen to correctly display that information.
I am not a graphic designer and even I can look at my report and think that a 6th grader taking a beginner class on HTML could have laid this thing out. I would greatly appreciate feedback (modified code or updated CSS would also be appreciated) to make this look better. I would also appreciate comments on attributes to add to the report.
In addition to that, there is a comment on line 104 that documents a query, that for some reason I get no results when I run it. I would love to know what I am missing there. I am sure it is something simple. I also need help on line 243. I have put the columns I am going to display in an array, but I would like the option for the caller to specify additional attributes to include, but I cannot seem to work out the ForEach loop to do this. The version there is the closest I have gotten so far, but it only adds the last attribute and repeats it. It has to do with the last value of $a, but I cannot figure out how to combine “$_” the value on the pipeline currently, with “$_” the value on the pipeline when it comes time for the select-object cmdlet. Uncomment it, supply a couple of attribute names in $IdentityAttributes and you will see what I mean.
To run this yourself, you just need to have the SailPoint PSSailPoint module installed and configured with your PAT. I am also half-hoping that someone will tell me I wasted my time and that this can be done in the access intellegence center.
<#
.SYNOPSIS
Generate a report of the specified role
.DESCRIPTION
Generates an HTML file detailing the specified role(s) and its composition
.PARAMETER Role
The name or ID of one or more roles to analyze
.PARAMETER Path
The location to save the reports to. Should NOT include a file name.
The file name will be the same as the name of the role. One role per file.
.EXAMPLE
Get-ISCRoleDefinition -Name “My Role” -Path “./Reports”
.EXAMPLE
Get-ISCRoleDefinition -Name “Role1”, “Supervisor” -Path “d:/role reports”
#>
Function Get-ISCRoleDefinition {
[CmdletBinding()]
param (
# Name or ID of the role to analyze
[Parameter(Mandatory=$true, Position=0)]
[String
]
$Role,
# Additional attributes to include for people referenced on the report
[string
]$IdentityAttributes,
# Your custom cascading style sheet. Include tags.
[string]$Style,
# Where to save the report
[Parameter(Mandatory=$true, Position=1)]
[String]
$Path
)
Begin {
# Get information about the tenant so I can create the URLs correctly
$tenant = Get-V2025Tenant
$tenantURL = ($tenant.products | where productName -eq "idn").url
If ($null -eq $tenantURL) { $url = "https://$($tenant.name).identitynow.com" }
$roleURLPath = "/ui/a/admin/access/roles/landing-page/details/"
$accessProfileURLPath = "/ui/a/admin/access/access-profiles/landing/details/"
$entitlementURLPath = "/ui/a/admin/access/entitlements/landing-page/details/"
$identityURLPath = "/ui/a/admin/identities/{0}/details/attributes"
$hrefparm = "target=""_blank"" rel=""noopener noreferrer"""
$css = @"
<style>
h1 {font-family: Arial, Helvetica, sans-serif;
color: #01411a;
font-size: 24px;
}
h2 {font-family: Arial, Helvetica, sans-serif;
color: #0cbe24;
font-size: 16px;
}
table {
font-size: 12px;
border: 0px;
font-family: Arial, Helvetica, sans-serif;
}
td {
padding: 4px;
margin: 0px;
border: 0;
}
th {
background: #147419;
background: linear-gradient(#49708f, #145722);
color: #fff;
font-size: 11px;
padding: 4px 4px;
vertical-align: middle;
}
tbody tr:nth-child(even) {
background: #f0f2f1;
}
.roleLabel {
font-weight: bold
}
.roleValue {
padding: 4px
}
.roleProperties {
}
</style>
"@
If ($Style) { $css = $Style }
}
Process {
ForEach($r in $Role) {
Write-Host "Examining role '$r'"
# For some reason, the query id eq ""$r"" or name eq ""$r"" does not work, but I can split them up and it does.
Try {
$query = "name eq ""$r"""
Write-Verbose "Query $query"
$roleDef = Get-Roles -Filters $query
} catch {}
If ($null -eq $roleDef) {
Try {
$query = "id eq ""$r"""
Write-Verbose "Query $query"
$roleDef = Get-Roles -Filters $query
} catch {}
}
If ($null -eq $roleDef) {
Write-Warning "Role $r was not found."
} else {
Write-Host "`t$($roleDef.name) ($($roleDef.id))"
# Gather composition
Write-Host "`tAnalyzing composition"
$entitlements = [system.collections.generic.list[object]]::new()
$accessProfiles = [system.collections.generic.list[object]]::new()
$apEntitlements = [system.collections.generic.list[object]]::new()
ForEach ($item in $roleDef.entitlements) {
Write-Verbose "Retreiving entitlement $($item.name)"
$entitlements.Add((Get-V2025Entitlement -Id $item.id))
}
Write-host "`t`t$($entitlements.count) entitlements assigned to role."
ForEach ($item in $roleDef.accessProfiles) {
Write-Verbose "Retreiving access profile $($item.name)"
$accessProfiles.Add((Get-AccessProfile -Id $item.id))
}
Write-Host "`t`t$($accessProfiles.count) accessProfiles assigned to role."
Write-Verbose "Examining the entitlements that compose the access profiles."
ForEach($ap in $accessProfiles) {
Write-Host "`t`t$($ap.name)"
ForEach($item in $ap.entitlements) {
$e = Get-V2025Entitlement -Id $item.id
If ($null -ne $e) {
Write-Host "`t`t`t$($e.name)"
Try {
$o = [pscustomobject]@{"apName"=$ap.name;"entitlement"=$e}
$apEntitlements.Add($o)
} Catch {
Write-Warning "Failed to add entitlement to access profile definition. ($($_.Exception.Message))"
Write-Warning "Access Profile: $($ap.name) - Entitlement: $($item.name) ($($item.id))"
}
}
}
}
Write-Host "`t`t$($apEntitlements.count) entitlements found in the access profiles."
# now I need the owner information
$people = @{}
$allOwners = [system.collections.generic.list[object]]::new()
# Start with the role's owner
If ($roleDef.owner -is [hashtable]) {
$item = Get-V2025Identity -Id $roleDef.owner.id
If ($null -ne $item) { $people.Add($item.id, $item)}
} else {
$roleDef.owner | ForEach-Object {$allOwners.Add($_)}
}
If ($entitlements.owner -is [hashtable]) {
$item = Get-V2025Identity -Id $entitlements.owner.id
If ($null -ne $item -and $people.ContainsKey($item.id) -eq $false) { $people.Add($item.id, $item)}
} else {
If ($entitlements.owner) { $entitlements.owner | ForEach-Object {$allOwners.Add($_)} }
}
If ($accessProfiles.owner -is [hashtable]) {
$item = Get-V2025Identity -Id $accessProfiles.owner.id
If ($null -ne $item -and $people.ContainsKey($item.id) -eq $false) { $people.Add($item.id, $item)}
} else {
If ($accessProfiles.owner) { $accessProfiles.owner | ForEach-Object {$allOwners.Add($_)} }
}
If ($apEntitlements.entitlement.owner -is [hashtable]) {
$item = Get-V2025Identity -Id $$apEntitlements.entitlement.owner.id
If ($null -ne $item -and $people.ContainsKey($item.id) -eq $false) { $people.Add($item.id, $item)}
} else {
If ($apEntitlements.entitlement.owner) { $apEntitlements.entitlement.owner | ForEach-Object {$allOwners.Add($_)} }
}
ForEach($owner in $allOwners) {
If ($owner.type -eq "IDENTITY") {
If ($people.ContainsKey($owner.id) -eq $false) {
# Add this person to the list.
$item = Get-V2025Identity -Id $owner.id
If ($null -ne $item) { $people.Add($owner.id, $item)}
}
}
}
# Output the information to HTML
$body ="<a href=""$($tenantURL)$($roleURLPath)$($roleDef.id)"" $hrefparm><h1 class=""rolename"">$($roleDef.name)</h1></a>"
$body += "<p class=""roleDescr"">$($roleDef.description)</p>"
$body += "<p class=""roleProperties"">`r`n"
$body += "`t<table class=""roleProperties"">`r`n"
$body += "`t`t<tr><td colspan=""4""><span class=""roleLabel roleId"">ID:</span><span class=""roleValue roleId""><a href=""$($tenantURL)$($roleURLPath)$($roleDef.id)"" target=""_blank"" rel=""noopener noreferrer"">$($roleDef.id)</a></span></td></tr>`r`n"
$body += "`t`t<tr><td colspan=""4""><span class=""roleLabel roleOwner"">Owner:</span><span class=""roleValue roleOwner""><a href=""#p$($roleDef.owner.id)"">$($roleDef.owner.name)</a></span></td></tr>"
$body += "`t`t<tr><td><span class=""roleLabel roleEnabled"">Enabled:</span><span class=""roleValue roleEnabled"">$($roleDef.enabled)</span></td>"
$body += "<td><span class=""roleLabel roleRequestable"">Requestable:</span><span class=""roleValue roleRequestable"">$($roleDef.enabled)</span></td></tr>"
$body += "`t`t<tr><td><span class=""roleLabel roleCreated"">Created:</span><span class=""roleValue roleCreated"">$($roleDef.created)</span></td>"
$body += "<td><span class=""roleLabel roleModified"">Modified:</span><span class=""roleValue roleModifed"">$($roleDef.modified)</span></td></tr>"
If ($roleDef.requestable -eq $true) {
# $body += (Convert-AccessRequestConfigToHTML -RequestConfig $roleDef.accessRequestConfig)
}
$body += "</table>"
$body += "</p>"
# Role owner, requestable, privileged, rules
$body += "<h2 class=""contentHeader"">Entitlements</h2>`r`n"
$columns = @{"n"="source";"e"={$_.source.name}}, @{"n"="name";"e"={"<a href=""#e$($_.id)"">$($_.name)</a>"}}, @{"n"="objType";"e"={$_.sourceSchemaObjectType}}, "privileged", @{"n"="owner";"e"={"<a href=""#p$($_.owner.id)"">$($_.owner.name)</a>"}}
$body += $entitlements | Sort-Object name | Select-Object $columns | ConvertTo-HTML -Fragment | ForEach-Object { [System.Web.HttpUtility]::HtmlDecode($_) }
$body += "`r`n"
$body += "<h2 class=""contentHeader"">Access Profiles</h2>`r`n"
$columns = @{"n"="source";"e"={$_.source.name}}, @{"n"="name";"e"={"<a href=""#a$($_.id)"" $hrefparm>$($_.name)</a>"}}, "description", "enabled", @{"n"="owner";"e"={"<a href=""#p$($_.owner.id)"">$($_.owner.name)</a>"}}
$body += $accessProfiles | Sort-Object name | Select-Object $columns | ConvertTo-HTML -Fragment | ForEach-Object { [System.Web.HttpUtility]::HtmlDecode($_) }
$body += "`r`n"
$body += "<h2 class=""contentHeader"">Access Profile Detail</h2>`r`n"
ForEach ($item in $accessProfiles | sort-Object name) {
$body += "<div id=""a$($item.id)""><a href=""$($tenantURL)$($accessProfileURLPath)$($item.id)"" $hrefparm><h3 class=""contentHeader"">$($item.name)</h3></a></div>`r`n"
$columns = @{"n"="entitlementName";"e"={"<a href=""#e$($_.entitlement.id)"">$($_.entitlement.name)</a>"}},@{"n"="source";"e"={$_.entitlement.source.name}}, @{"n"="objType";"e"={$_.entitlement.sourceSchemaObjectType}}, @{"n"="privileged";"e"={$_.entitlement.privileged}}, @{"n"="owner";"e"={"<a href=""#p$($_.entitlement.owner.id)"">$($_.entitlement.owner.name)</a>"}}
$body += $apentitlements | Where-Object apName -eq $item.name | Select-Object $columns | ConvertTo-HTML -Fragment | ForEach-Object { [System.Web.HttpUtility]::HtmlDecode($_) }
$body += "`r`n"
}
# Entitlement details
$body += "<h2 class=""contentHeader"">Entitlement Detail</h2>`r`n"
$body += ($apentitlements.entitlement + $entitlements) | Sort-Object name -Unique |
Select-Object @{"n"="name";"e"={"<div id=""e$($_.id)""><a href=""$($tenantURL)$($entitlementURLPath)$($_.id)"" $hrefparm>$($_.name)</a></div>"}} `
, description, privileged, requestable, @{"n"="source";"e"={$_.source.name}} `
, @{"n"="owner";"e"={"<a href=""#p$($_.owner.id)"">$($_.owner.name)</a>"}} `
, created, modified, @{"n"="objType";"e"={$_.attribute}} `
| ConvertTo-Html -Fragment | ForEach-Object { [System.Web.HttpUtility]::HtmlDecode($_) }
# People details
$body += "<h2 class=""contentHeader"">Owner Detail</h2>`r`n"
$columns = @{"n"="name";"e"={"<div id=""p$($_.id)""><a href=""$($tenantURL)$($identityURLPath -f $_.id)"" $hrefparm>$($_.name)</a></div>"}}, "emailAddress", "identityStatus"
# **** I need help here **** How do I take an array of attribute names, convert them to a hashtable, then append them to an array?
#If ($IdentityAttributes) { $columns += ($IdentityAttributes | ForEach-Object { $a = $_; @{"n"="$_";"e"= {$_.attributes.$a}} } )}
$body += $people.Values | Sort-object name | Select-Object $columns | ConvertTo-Html -Fragment | ForEach-Object { [System.Web.HttpUtility]::HtmlDecode($_) }
$body += "`r`n"
$preHeader = "<!DOCTYPE html PUBLIC ""-//W3C//DTD XHTML 1.0 Strict//EN"" ""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"">`r`n"
$header = "<header>`r`n`t<title>$($roleDef.name) Definition</title>`r`n$css`r`n</header>"
$footer = "<footer style=""text-align: center;"">Generated on $((Get-Date).ToString("MM/dd/yyyy")) at $((Get-Date).ToString("HH:mm"))"
"$preHeader<html xmlns=""http://www.w3.org/1999/xhtml"">`r`n$header<body>$body</body>$footer</html>" | Set-Content -Path "$Path\$($roleDef.name) Definition.html"
} # End If roleDef
}
}
End {}
}
The HTML file produced will link you to other areas of the report and then for some items the hyperlink will take you to the page in ISC that covers the item.
Again, I could really use some guidence to make this a more professional looking report, and in exchange, I am offering up my work to the community for their use.