Skip to content

Instantly share code, notes, and snippets.

@7effrey89
Last active January 23, 2026 22:12
Show Gist options
  • Select an option

  • Save 7effrey89/876f855c9c8cb07a9753ee6cc50886b9 to your computer and use it in GitHub Desktop.

Select an option

Save 7effrey89/876f855c9c8cb07a9753ee6cc50886b9 to your computer and use it in GitHub Desktop.
OneLake Security Rest API
[CmdletBinding()]
# Summary:
# Standalone validation utility that provisions a Fabric OneLake data access role
# (default: GoldReader) on the specified lakehouse and assigns a Microsoft Entra
# user to it using the documented preview endpoints. Useful for testing role
# creation/membership independently of the full workspace automation.
#how to run script:
#Onelake security must be enabled on the target lakehouse for this script to work.
#pwsh -NoLogo -NoProfile -Command "& 'c:/Git/Fabric_powershell/Standalone scripts/Test-OneLakeRoleAssignment.ps1' -WorkspaceName '001onelakesec' -LakehouseName 'Gold' -TenantId 'guid-guid-guid' -UserObjectId 'guid-guid-guid' -RoleName 'GoldReader'"
param(
[Parameter(Mandatory=$true)][string]$WorkspaceName,
[Parameter(Mandatory=$true)][string]$LakehouseName,
[Parameter(Mandatory=$true)][string]$TenantId,
[Parameter(Mandatory=$true)][string]$UserObjectId,
[string]$RoleName = "GoldReader",
[string[]]$PathScope = @("*"),
[string[]]$ActionScope = @("Read"),
[string]$ClientId = "",
[string]$ClientSecret = ""
)
$ErrorActionPreference = "Stop"
$global:baseUrl = "https://api.fabric.microsoft.com/v1"
$global:resourceUrl = "https://api.fabric.microsoft.com"
$global:fabricHeaders = @{}
function ConvertSecureStringToPlainText {
param([System.Security.SecureString]$SecureString)
$ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString)
try {
return [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ptr)
}
finally {
[System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr)
}
}
function GetSecureTokenForUserPrincipal {
if (-not (Get-AzContext -ErrorAction SilentlyContinue)) {
Write-Host "Connecting to Azure interactively..." -ForegroundColor Cyan
Connect-AzAccount -TenantId $TenantId | Out-Null
}
Write-Host "Acquiring Fabric access token (user)..." -ForegroundColor Cyan
return (Get-AzAccessToken -AsSecureString -ResourceUrl $global:resourceUrl).Token
}
function GetSecureTokenForServicePrincipal {
if ([string]::IsNullOrWhiteSpace($ClientSecret)) {
throw "ClientSecret must be provided when using ClientId."
}
$secureSecret = ConvertTo-SecureString -String $ClientSecret -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential ($ClientId, $secureSecret)
Write-Host "Connecting with Service Principal..." -ForegroundColor Cyan
Connect-AzAccount -ServicePrincipal -TenantId $TenantId -Credential $credential | Out-Null
Write-Host "Acquiring Fabric access token (service principal)..." -ForegroundColor Cyan
return (Get-AzAccessToken -AsSecureString -ResourceUrl $global:resourceUrl).Token
}
function SetFabricHeaders {
$useServicePrincipal = -not [string]::IsNullOrWhiteSpace($ClientId)
$secureToken = if ($useServicePrincipal) { GetSecureTokenForServicePrincipal } else { GetSecureTokenForUserPrincipal }
$token = ConvertSecureStringToPlainText -SecureString $secureToken
$global:fabricHeaders = @{
"Content-Type" = "application/json"
"Authorization" = "Bearer $token"
}
Write-Host "Fabric headers initialized." -ForegroundColor Green
}
function GetErrorResponse {
param([System.Management.Automation.ErrorRecord]$ErrorRecord)
if ($ErrorRecord.ErrorDetails -and $ErrorRecord.ErrorDetails.Message) {
return $ErrorRecord.ErrorDetails.Message
}
$exception = $ErrorRecord.Exception
if (-not $exception -or -not $exception.Response) {
return $exception?.Message
}
try {
$stream = $exception.Response.GetResponseStream()
if (-not $stream) {
return $exception.Message
}
$reader = [System.IO.StreamReader]::new($stream)
$reader.BaseStream.Position = 0
$reader.DiscardBufferedData()
return $reader.ReadToEnd()
}
catch {
return $exception.Message
}
}
function GetWorkspaceByName {
param([string]$Name)
$url = "$global:baseUrl/workspaces"
$response = Invoke-RestMethod -Headers $global:fabricHeaders -Uri $url -Method GET
$workspace = $response.value | Where-Object { $_.displayName -eq $Name } | Select-Object -First 1
if (-not $workspace) {
throw "Workspace '$Name' not found."
}
Write-Host "Workspace '$Name' resolved to ID $($workspace.id)." -ForegroundColor Green
return $workspace
}
function GetLakehouseByName {
param([string]$WorkspaceId, [string]$Name)
$itemsUrl = "$global:baseUrl/workspaces/$WorkspaceId/items"
$items = Invoke-RestMethod -Headers $global:fabricHeaders -Uri $itemsUrl -Method GET
$lakehouse = $items.value | Where-Object { $_.displayName -eq $Name -and $_.type -eq "Lakehouse" } | Select-Object -First 1
if (-not $lakehouse) {
throw "Lakehouse '$Name' not found in workspace $WorkspaceId."
}
Write-Host "Lakehouse '$Name' resolved to ID $($lakehouse.id)." -ForegroundColor Green
return $lakehouse
}
function NewOneLakeRoleDefinition {
param(
[string]$RoleName,
[string[]]$PathScope,
[string[]]$ActionScope
)
if (-not $PathScope -or $PathScope.Count -eq 0) {
$PathScope = @("*")
}
if (-not $ActionScope -or $ActionScope.Count -eq 0) {
$ActionScope = @("Read")
}
return @{
name = $RoleName
decisionRules = @(
@{
effect = "Permit"
permission = @(
@{
attributeName = "Path"
attributeValueIncludedIn = $PathScope
},
@{
attributeName = "Action"
attributeValueIncludedIn = $ActionScope
}
)
}
)
members = @{
microsoftEntraMembers = @()
fabricItemMembers = @()
}
}
}
function GetOneLakeDataAccessRoles {
param([string]$WorkspaceId, [string]$LakehouseId)
$url = "$global:baseUrl/workspaces/$WorkspaceId/items/$LakehouseId/dataAccessRoles?preview=true"
try {
$response = Invoke-RestMethod -Headers $global:fabricHeaders -Uri $url -Method GET
$roles = if ($response.value) { $response.value } else { $response }
if (-not $roles) {
return @()
}
return @($roles)
}
catch {
$err = GetErrorResponse -ErrorRecord $_
if ($err -and $err -match "UniversalSecurityFeatureDisabledForWorkspace") {
throw [System.InvalidOperationException] "OneLakeSecurityDisabled"
}
throw
}
}
function SetOneLakeDataAccessRoles {
param([string]$WorkspaceId, [string]$LakehouseId, [object[]]$Roles)
$payload = @{ value = @($Roles) } | ConvertTo-Json -Depth 30
$url = "$global:baseUrl/workspaces/$WorkspaceId/items/$LakehouseId/dataAccessRoles?preview=true"
try {
Invoke-RestMethod -Headers $global:fabricHeaders -Uri $url -Method PUT -Body $payload | Out-Null
}
catch {
$err = GetErrorResponse -ErrorRecord $_
if ($err -and $err -match "UniversalSecurityFeatureDisabledForWorkspace") {
throw [System.InvalidOperationException] "OneLakeSecurityDisabled"
}
throw "Failed to persist OneLake data access roles: $err"
}
}
function GetOneLakeDataAccessRoleDefinition {
param([string]$WorkspaceId, [string]$LakehouseId, [string]$RoleName)
$roles = GetOneLakeDataAccessRoles -WorkspaceId $WorkspaceId -LakehouseId $LakehouseId
return $roles | Where-Object { $_.name -eq $RoleName } | Select-Object -First 1
}
function EnsureOneLakeDataAccessRole {
param(
[string]$WorkspaceId,
[string]$LakehouseId,
[string]$RoleName,
[string[]]$PathScope,
[string[]]$ActionScope
)
$roles = @()
try {
$roles = GetOneLakeDataAccessRoles -WorkspaceId $WorkspaceId -LakehouseId $LakehouseId
}
catch [System.InvalidOperationException] {
if ($_.Exception.Message -eq "OneLakeSecurityDisabled") {
Write-Warning "OneLake security preview is disabled for this lakehouse."
return $false
}
throw
}
$existingRole = $roles | Where-Object { $_.name -eq $RoleName } | Select-Object -First 1
if ($existingRole) {
Write-Host "Role '$RoleName' already exists." -ForegroundColor Yellow
return $true
}
$definition = NewOneLakeRoleDefinition -RoleName $RoleName -PathScope $PathScope -ActionScope $ActionScope
$updatedRoles = @($roles)
$updatedRoles += $definition
try {
SetOneLakeDataAccessRoles -WorkspaceId $WorkspaceId -LakehouseId $LakehouseId -Roles $updatedRoles
Write-Host "Provisioned OneLake data access role '$RoleName'." -ForegroundColor Green
return $true
}
catch [System.InvalidOperationException] {
if ($_.Exception.Message -eq "OneLakeSecurityDisabled") {
Write-Warning "OneLake security preview is disabled for this lakehouse."
return $false
}
throw
}
}
function UpdateOneLakeRoleMembers {
param(
[string]$WorkspaceId,
[string]$LakehouseId,
[psobject]$RoleDefinition,
[string]$UserObjectId,
[string]$TenantId
)
# Fetch entire role collection to keep server-side state consistent.
$roles = @()
try {
$roles = GetOneLakeDataAccessRoles -WorkspaceId $WorkspaceId -LakehouseId $LakehouseId
}
catch [System.InvalidOperationException] {
if ($_.Exception.Message -eq "OneLakeSecurityDisabled") {
Write-Warning "OneLake security preview is disabled for this lakehouse."
return
}
throw
}
$roles = @($roles)
$targetRole = $roles | Where-Object { $_.name -eq $RoleDefinition.name } | Select-Object -First 1
if (-not $targetRole) {
$targetRole = $RoleDefinition
$roles += $targetRole
}
if (-not $targetRole.members) {
$targetRole | Add-Member -MemberType NoteProperty -Name members -Value @{} -Force
}
$entraMembers = @()
if ($targetRole.members.microsoftEntraMembers) {
$entraMembers = @($targetRole.members.microsoftEntraMembers)
}
if ($entraMembers | Where-Object { $_.objectId -eq $UserObjectId }) {
Write-Host "User already assigned to role '$($targetRole.name)'." -ForegroundColor Yellow
return
}
$entraMembers += @{
objectId = $UserObjectId
objectType = "User"
tenantId = $TenantId
}
$targetRole.members.microsoftEntraMembers = $entraMembers
if (-not $targetRole.members.fabricItemMembers) {
$targetRole.members.fabricItemMembers = @()
}
SetOneLakeDataAccessRoles -WorkspaceId $WorkspaceId -LakehouseId $LakehouseId -Roles $roles
Write-Host "Assigned user to role '$($targetRole.name)'." -ForegroundColor Green
}
function Invoke-OneLakeRoleTest {
Write-Host "\n--- OneLake Role Test ---" -ForegroundColor Cyan
Write-Host "Workspace: $WorkspaceName" -ForegroundColor Cyan
Write-Host "Lakehouse: $LakehouseName" -ForegroundColor Cyan
Write-Host "Role: $RoleName" -ForegroundColor Cyan
SetFabricHeaders
$workspace = GetWorkspaceByName -Name $WorkspaceName
$lakehouse = GetLakehouseByName -WorkspaceId $workspace.id -Name $LakehouseName
$provisioned = EnsureOneLakeDataAccessRole -WorkspaceId $workspace.id -LakehouseId $lakehouse.id -RoleName $RoleName -PathScope $PathScope -ActionScope $ActionScope
if (-not $provisioned) {
Write-Warning "Stopping test because OneLake security is disabled."
return
}
$roleDefinition = GetOneLakeDataAccessRoleDefinition -WorkspaceId $workspace.id -LakehouseId $lakehouse.id -RoleName $RoleName
if (-not $roleDefinition) {
$roleDefinition = NewOneLakeRoleDefinition -RoleName $RoleName -PathScope $PathScope -ActionScope $ActionScope
}
if ($roleDefinition.PSObject.Properties.Name -contains "id") {
Write-Host "Role '$RoleName' id: $($roleDefinition.id)" -ForegroundColor Gray
}
UpdateOneLakeRoleMembers -WorkspaceId $workspace.id -LakehouseId $lakehouse.id -RoleDefinition $roleDefinition -UserObjectId $UserObjectId -TenantId $TenantId
Write-Host "OneLake role test complete." -ForegroundColor Green
}
Invoke-OneLakeRoleTest
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment