Use Azure Powershell Runbook to add AzureAD user as db_owner to an Azure SQL database - azure

I have a Powershell Runbook where I am trying to add AzureAD user as Database owner on an Azure SQL database.
## Connect
$servicePrincipalConnection = Get-AutomationConnection -Name "AzureRunAsConnection"
Connect-AzureAD `
-TenantId $servicePrincipalConnection.TenantId `
-ApplicationId $servicePrincipalConnection.ApplicationId `
-CertificateThumbprint $servicePrincipalConnection.CertificateThumbprint
## Generate new access token
$cert = Get-AutomationCertificate -Name 'AzureRunAsCertificate'
# Set Resource URI to Azure Database
$resourceAppIdURI = 'https://database.windows.net/'
# Set Authority to Azure AD Tenant
$authority = 'https://login.windows.net/' + $servicePrincipalConnection.TenantId
$ClientCred = [Microsoft.IdentityModel.Clients.ActiveDirectory.ClientAssertionCertificate]::new($servicePrincipalConnection.ApplicationId, $cert)
$authContext = [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext]::new($authority)
$authResult = $authContext.AcquireTokenAsync($resourceAppIdURI, $ClientCred)
$AccessToken = $authResult.Result.AccessToken
## Execute sql
$AccessToken
$connectionString = "Data Source=MYCOOLSQLSERVER.database.windows.net;Initial Catalog=MYCOOLDATABASE;Connect Timeout=30"
$connection = New-Object -TypeName System.Data.SqlClient.SqlConnection($connectionString)
$query = "Create User [abc#xyz.com] From EXTERNAL PROVIDER;"
$command = New-Object -TypeName System.Data.SqlClient.SqlCommand($query, $connection)
$connection.AccessToken = $AccessToken
$connection.Open()
$command.ExecuteNonQuery()
$connection.Close()
I end up getting the error below where abc#xyz.com is an AzureAD user.
Principal 'abc#xyz.com' could not be resolved.
Is there something that I have missed out?
Once the user gets created, I intend to use Alter role to make him the db_owner.
References:
https://blogs.technet.microsoft.com/stefan_stranger/2018/06/06/connect-to-azure-sql-database-by-obtaining-a-token-from-azure-active-directory-aad/
https://techcommunity.microsoft.com/t5/Azure-Database-Support-Blog/Azure-SQL-Database-Token-based-authentication-with-PowerShell/ba-p/369078

Turns out that there is an undocumented way to do this. Discovered it with the help of Azure support team.
The SQL query to be used is actually:
CREATE USER [abc#xyz.com] WITH SID = $hexSid, Type E;
Here $hexSid can be obtained by the following SQL query
Get the ObjectId of the AzureAD User
$objectId = (Get-AzureADUser -ObjectId "abc#xyz.com").ObjectId
Use the SQL below to get the sid
DECLARE #sid = uniqueidentifier = cast('$objectId' as
uniqueidentifier) select cast(#sid as varbinary(max))
Now ideally, the effective query would have directly used the #sid but that is not possible since the With SID needs a hard coded value. So I had to process the output of the sid query separately like below:
$result = $command.ExecuteScalar() # result is byte array
$hexSid = ($result | ForEach-Object ToString X2) -Join '' # convert to hex format
$hexSid = "0x$hexSid"
With those changes the code in the question just works fine!
Effectively, we are only passing the AzureAD User's objectId in the Create User query.
Complete code:
$targetUser = "abc#xyz.com"
$sqlServerName = "someserver"
$dbName = "somedb"
$accessToken = "" # obtained from SPN connection
# Get SQL SID for the targeted user
Write-Output "Getting AzureADUser $targetUser "
$objectId = (Get-AzureADUser -ObjectId $targetUser).ObjectId
$sidQuery = "DECLARE #sid uniqueidentifier = cast('$objectId' as uniqueidentifier) select cast(#sid as varbinary(max))"
$sidOutput = New-Object -TypeName HashTable
Write-Output "Invoking SQL Query execution to get SID for objectId: $objectId ($targetUser)"
Invoke-OrExecuteSqlQuery -ServerName $sqlServerName -DbName $dbName -Query $sidQuery `
-AccessToken $accessToken -Output $sidOutput
$sidBytes = $sidOutput.Result
Write-Output "SID bytes: $sidBytes"
$hexSid = ($sidBytes | ForEach-Object ToString X2) -Join '' # convert to hex format
$hexSid = "0x$hexSid"
Write-Output "SQL SID is : $hexSid"
# Add AzureAD user to the database
Write-Output "Adding AzureAD user : $targetUser to the db/dw: $dbName"
$createDBUserQuery = "CREATE USER [$targetUser] WITH SID = $hexSid, Type = E;" # create db user
$createUserOutput = New-Object -TypeName HashTable
Invoke-OrExecuteSqlQuery -ServerName $sqlServerName -DbName $dbName -Query $createDBUserQuery `
-AccessToken $accessToken -Output $createUserOutput
Write-Output "Created DB User : $targetUser on the DB : $dbName"
# Set targeted user as the database owner
Write-Output "Setting database admin for db/dw: $dbName"
$makeDBOwnerQuery = "ALTER ROLE db_owner ADD MEMBER [$targetUser];"
$alterSQLDBRoleOutput = New-Object -TypeName HashTable
Invoke-OrExecuteSqlQuery -ServerName $sqlServerName -DbName $dbName -Query $makeDBOwnerQuery `
-AccessToken $accessToken -Output $alterSQLDBRoleOutput
Write-Output "Made $targetUser as the DB Owner"
function Invoke-OrExecuteSqlQuery {
param (
[string] $ServerName,
[string] $DbName,
[string] $Query,
[string] $AccessToken,
[hashtable] $Output
)
$server = "$ServerName.database.windows.net"
$database = $DbName
$query = $Query
Write-Output "Connecting to the SQL DB: $database on the SQL Server: $server"
$connectionString = "Data Source=$server;Initial Catalog=$database;Connect Timeout=30;"
$connection = New-Object -TypeName System.Data.SqlClient.SqlConnection($connectionString)
$connection.AccessToken = $AccessToken
Write-Output "Executing SQL query.."
$command = New-Object -TypeName System.Data.SqlClient.SqlCommand($query, $connection)
$connection.Open()
$result = $command.ExecuteScalar()
$connection.Close()
Write-Output "Execution of SQL query complete"
if ($result) {
$Output.Result = $result
}
}

for this scenario, we should use user password flow to make this command work. Pls try the PS below :
$tenant='<your tenant name/id>'
$username='<SQL admin account>'
$password='<SQL admin password>'
$appid='<app id>'
$appsec='<app secret>'
$SQLServerName = '<azure sql servername>'
$DatabaseName='<db name>'
$body=#{
    "grant_type"="password";
    "resource"="https://database.windows.net/";
    "client_id"=$appid;
"client_secret"=$appsec;
    "username"=$username;
    "password" = $password;
}
 
$result=Invoke-RestMethod -Uri "https://login.windows.net/$tenant/oauth2/token" -Method POST -Body $body 
$conn = New-Object System.Data.SqlClient.SQLConnection
$conn.ConnectionString = "Data Source=$SQLServerName.database.windows.net;Initial Catalog=$DatabaseName;Connect Timeout=30"
$conn.AccessToken = $result.access_token
Write-Verbose "Connect to database and execute SQL script"
$conn.Open()
$query = 'CREATE USER [Account] FROM EXTERNAL PROVIDER;'
$command = New-Object -TypeName System.Data.SqlClient.SqlCommand($query, $conn)
$Result = $command.ExecuteScalar()
$Result
$conn.Close()
Before you run is script, you should grant user_impersonation Azure SQL API access permission for your SPN in Azure AD here :
Grant permission after added :
Btw, pls makre sure that the SQL admin account and SPN are all members of Active Directory admin group :
It works on my side and a record has been added successfully :
If there is anything unclear , pls feel free to let me know : )

Related

How can i retrieve username and password when retrieving Azure Database details via Powershell?

I can list Azure databases as follows:
$servers = Get-AzSqlServer
foreach($server in $servers)
{
$databases = Get-AzSqlDatabase -ServerName $server.ServerName -ResourceGroupName
$server.ResourceGroupName
foreach($db in $databases){
Write-Host "Server " $db.ServerName "Database " $db.DatabaseName
$server.SqlAdministratorLogin "SqlAdminPwd "
$server.SqlAdministratorPassword
Invoke-SqlCmd -ServerInstance $db.ServerName -Database $db.DatabaseName -
Username <azuredb_admin_username> -Password
<azuredb_admin_password> -Query
'select * from [dbo].[TableName]'
}
}
$server.SqlAdministratorPassword is empty which is understandable.
But in the next line I invoke a sqlcmd which requires the password.
How can i get and use the password ?
PowerShell query should require login credential implicitly or explicitly through the command line. There are couple of ways to pass credentials.
Store credentials in credentials manager and fetch the information from there
example:
$psCred = Get-StoredCredential -Target "ABCD" –AsCredentialObject
Here is the code snippet to get the password using PowerShell
$serverName = $db.ServerName
$DB = $db.DatabaseName
$credential = Get-Credential
$userName = $credential.UserName.Replace("\","")
$pass = $credential.GetNetworkCredential().password
invoke-sqlcmd -query "select ##Servername" -database $DB -serverinstance $servername -username $username -password $pass -Query 'select * from [dbo].[TableName]'
Hope this helps!

Get-PnPTenantSite : Attempted to perform an unauthorized operation

Currently we get an access token and then pass this token to PowerShell script to loop across all ODFB personal sites.
$url = "https://XXXXX-admin.sharepoint.com"
$conn = Connect-PnPOnline -Url $url -AccessToken $access_token -ReturnConnection
$sitecollections = Get-PnPTenantSite -IncludeOneDriveSites:$true -Filter "Url -like '-my.sharepoint.com/personal/'" -Connection $conn | Select-Object -ExpandProperty Url
foreach ($site in $sitecollections)
{
....
}
It worked successfully for years until it was broken a while ago.
I tried different versions of PnP PowerShell:
PnP version
Error
SharePointPnPPowerShellOnline 3.21.2005.2 (currently used)
Get-PnPTenantSite : Attempted to perform an unauthorized operation.
SharePointPnPPowerShellOnline 3.29.2101.0
Get-PnPTenantSite : The current connection holds no SharePoint context.
PnP.PowerShell 1.10.28
Get-PnPTenantSite : Attempted to perform an unauthorized operation.
If I change script to use an user/password instead the access token, the script works without problems:
$url = "https://XXXXX-admin.sharepoint.com"
$User = "admin#mydomain.com"
$PWord = ConvertTo-SecureString -String "Password" -AsPlainText -Force
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $User, $PWord
$conn = Connect-PnPOnline -Url $url -Credentials $Credential -ReturnConnection
$sitecollections = Get-PnPTenantSite -IncludeOneDriveSites:$true -Filter "Url -like '-my.sharepoint.com/personal/'" -Connection $conn | Select-Object -ExpandProperty Url
foreach ($site in $sitecollections)
{
....
}
So the error happens when the script connects to SP Online using an access token.
Perhaps the some things were changed. But what exactly? Have some scope to be added when an access token is requested?
Or have some new permissions to be added for the application in Azure AD?
Update:
Modified the script (added Write-Output "Connection is:" $conn | fl) to provide more details about connection and got the difference in ConnectionType property when SharePointPnPPowerShellOnline 3.21.2005.2 is used:
When an access token is used (and the script doesn't work properly), ConnectionType : O365
When an access token is used (and the script works fine), ConnectionType : TenantAdmin

In Azure SQL Server can the AD Admin which is also a Service Principal run a query on the master database?

Given:
An Azure SQL Server - MyAzureSqlServer
A Service Principal - MyServicePrincipal
The Service Principal is configured as the AD Admin of the Azure SQL Server. (Azure Portal and Az Powershell module do not allow it, but Azure CLI and the REST API do)
I have Powershell code that runs SELECT 1 on the given database in the aforementioned Azure SQL Server:
param($db)
$AzContext = Get-AzContext # Assume this returns the Az Context for MyServicePrincipal
$TenantId = $AzContext.Tenant.Id
$ClientId = $AzContext.Account.Id
$SubscriptionId = $AzContext.Subscription.Id
$ClientSecret = $AzContext.Account.ExtendedProperties.ServicePrincipalSecret
$token = Get-AzureAuthenticationToken -TenantID $TenantId -ClientID $ClientId -ClientSecret $ClientSecret -ResourceAppIDUri "https://database.windows.net/"
Invoke-SqlQueryThruAdoNet -ConnectionString "Server=MyAzureSqlServer.database.windows.net;database=$db" -AccessToken $token -Query "SELECT 1"
Where Get-AzureAuthenticationToken is:
function Get-AzureAuthenticationToken(
[Parameter(Mandatory)][String]$TenantID,
[Parameter(Mandatory)][String]$ClientID,
[Parameter(Mandatory)][String]$ClientSecret,
[Parameter(Mandatory)][String]$ResourceAppIDUri)
{
$tokenResponse = Invoke-RestMethod -Method Post -UseBasicParsing `
-Uri "https://login.windows.net/$TenantID/oauth2/token" `
-Body #{
resource = $ResourceAppIDUri
client_id = $ClientID
grant_type = 'client_credentials'
client_secret = $ClientSecret
} -ContentType 'application/x-www-form-urlencoded'
Write-Verbose "Access token type is $($tokenResponse.token_type), expires $($tokenResponse.expires_on)"
$tokenResponse.access_token
}
And Invoke-SqlQueryThruAdoNet is:
function Invoke-SqlQueryThruAdoNet(
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]
[string]$ConnectionString,
[parameter(Mandatory=$true)]
[string]$Query,
$QueryTimeout = 30,
[string]$AccessToken
)
{
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$SqlAdapter = New-Object System.Data.SqlClient.SqlDataAdapter
try
{
$SqlConnection.ConnectionString = $ConnectionString
if ($AccessToken)
{
$SqlConnection.AccessToken = $AccessToken
}
$SqlConnection.Open()
$SqlCmd.CommandTimeout = $QueryTimeout
$SqlCmd.CommandText = $Query
$SqlCmd.Connection = $SqlConnection
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.SelectCommand = $SqlCmd
[void]$SqlAdapter.Fill($DataSet)
$res = $null
if ($DataSet.Tables.Count)
{
$res = $DataSet.Tables[$DataSet.Tables.Count - 1]
}
$res
}
finally
{
$SqlAdapter.Dispose()
$SqlCmd.Dispose()
$SqlConnection.Dispose()
}
}
And it works fine on any database, except on the master, for which I get:
[MyAzureSqlServer.database.windows.net\master] Login failed for user '4...1#2...b'. (SqlError 18456, LineNumber = 65536, ClientConnectionId = b8f4f657-2772-4306-b222-4533013227d1)
where 4...1 is the client Id of MyServicePrincipal and 2...b is our Azure AD Tenant Id.
So I know the access token is OK, because I can run queries on other databases. It is specifically the master that is problematic. Is there a solution for that? Of course, it must work with the Service Principal being the AD Admin.
EDIT 1
As I have mentioned there are 2 ways to configure a Service Principal to be the AD Admin:
Using Azure CLI. It is actually straightforward:
az sql server ad-admin create --resource-group {YourAzureSqlResourceGroupName} `
--server-name {YourAzureSqlServerName} `
--display-name {ADAdminName} `
--object-id {ServicePrincipalObjectId}
The {ADAdminName} can be whatever, but we pass the display name of the Service Principal.
Now while this works, we abandoned Azure CLI in favour of Az Powershell, because the latter does not persist Service Principal credentials on disk in clear text. However, Az Powershell's function Set-AzSqlServerActiveDirectoryAdministrator does not accept a Service Principal. Yet the Azure REST API does allow it, hence we have the following custom PS function doing the job:
function Set-MyAzSqlServerActiveDirectoryAdministrator
{
[CmdLetBinding(DefaultParameterSetName = 'NoObjectId')]
param(
[Parameter(Mandatory, Position = 0)][string]$ResourceGroupName,
[Parameter(Mandatory, Position = 1)][string]$ServerName,
[Parameter(ParameterSetName = 'ObjectId', Mandatory)][ValidateNotNullOrEmpty()]$ObjectId,
[Parameter(ParameterSetName = 'ObjectId', Mandatory)][ValidateNotNullOrEmpty()]$DisplayName
)
$AzContext = Get-AzContext
if (!$AzContext)
{
throw "No Az context is found."
}
$TenantId = $AzContext.Tenant.Id
$ClientId = $AzContext.Account.Id
$SubscriptionId = $AzContext.Subscription.Id
$ClientSecret = $AzContext.Account.ExtendedProperties.ServicePrincipalSecret
if ($PsCmdlet.ParameterSetName -eq 'NoObjectId')
{
$sp = Get-AzADServicePrincipal -ApplicationId $ClientId
$DisplayName = $sp.DisplayName
$ObjectId = $sp.Id
}
$path = "/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroupName/providers/Microsoft.Sql/servers/$ServerName/administrators/activeDirectory"
$apiUrl = "https://management.azure.com${path}?api-version=2014-04-01"
$jsonBody = #{
id = $path
name = 'activeDirectory'
properties = #{
administratorType = 'ActiveDirectory'
login = $DisplayName
sid = $ObjectId
tenantId = $TenantId
}
} | ConvertTo-Json -Depth 99
$token = Get-AzureAuthenticationToken -TenantID $TenantId -ClientID $ClientId -ClientSecret $ClientSecret -ResourceAppIDUri "https://management.core.windows.net/"
$headers = #{
"Authorization" = "Bearer $token"
"Content-Type" = "application/json"
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-RestMethod $apiUrl -Method Put -Headers $headers -Body $jsonBody
}
It uses the already familiar (see above) function Get-AzureAuthenticationToken. For our needs it sets the currently logged in Service Principal as the AD admin.
According to my test, when we directly set Azure service principal as Azure SQL AD admin, it will cause some problems. We cannot log in master database with the service pricipal. Because Azure AD administrator login should be an Azure AD user or an Azure AD group. For more details, please refer to the document
So if you want to set Azure service principal as Azure SQL AD admin, we need to create an Azure AD security group, add service principal as the group's member the set the Azure AD group as Azure SQL AD admin.
For example
Configure Azure AD admin
Connect-AzAccount
$group=New-AzADGroup -DisplayName SQLADADmin -MailNickname SQLADADmin
$sp=Get-AzADServicePrincipal -DisplayName "TodoListService-OBO-sample-v2"
Add-AzADGroupMember -MemberObjectId $sp.Id -TargetGroupObjectId $group.id
$sp=Get-AzADServicePrincipal -DisplayName "<your sq name>"
Remove-AzSqlServerActiveDirectoryAdministrator -ResourceGroupName "<>" -ServerName "<>" -force
Set-AzSqlServerActiveDirectoryAdministrator -ResourceGroupName "<>" -ServerName "<>" -DisplayName $group.DisplayName -ObjectId $group.id
query
$appId = "<your sp app id>"
$password = "<your sp password>"
$secpasswd = ConvertTo-SecureString $password -AsPlainText -Force
$mycreds = New-Object System.Management.Automation.PSCredential ($appId, $secpasswd)
Connect-AzAccount -ServicePrincipal -Credential $mycreds -Tenant "<your AD tenant id>"
#get token
$context =Get-AzContext
$dexResourceUrl='https://database.windows.net/'
$token = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account,
$context.Environment,
$context.Tenant.Id.ToString(),
$null,
[Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never,
$null, $dexResourceUrl).AccessToken
$SqlConnection = New-Object System.Data.SqlClient.SqlConnection
$SqlCmd = New-Object System.Data.SqlClient.SqlCommand
$ConnectionString="Data Source=testsql08.database.windows.net; Initial Catalog=master;"
# query the current database name
$Query="SELECT DB_NAME()"
try
{
$SqlConnection.ConnectionString = $ConnectionString
if ($token)
{
$SqlConnection.AccessToken = $token
}
$SqlConnection.Open()
$SqlCmd.CommandText = $Query
$SqlCmd.Connection = $SqlConnection
$DataSet = New-Object System.Data.DataSet
$SqlAdapter.SelectCommand = $SqlCmd
[void]$SqlAdapter.Fill($DataSet)
$res = $null
if ($DataSet.Tables.Count)
{
$res = $DataSet.Tables[$DataSet.Tables.Count - 1]
}
$res
}
finally
{
$SqlAdapter.Dispose()
$SqlCmd.Dispose()
$SqlConnection.Dispose()
}

How to Add Api Permissions to an Azure App Registration using PowerShell

I am figure out the commands in Azure PowerShell to add an the User.Read Ape Permission to my App Registration in Azure.
I can find some examples using *Azure, but would prefer one that uses the *Az commands, e.g. https://learn.microsoft.com/en-us/powershell/azure/?view=azps-2.8.0.
Wonder if anybody knows how to do this? Thanks!
This can currently only be achieved using the Azure AD PowerShell. Please note that there is a difference between Azure AD PowerShell and Azure PowerShell. The Azure AD PowerShell is not simply the old Azure PowerShell module.
Azure AD PowerShell is a separate module. There is no "AZ*" for Azure AD yet. Only couple of most commonly used commands, that have Azure Resource Provider implementation.
Azure PowerShell has a limited set of features for working with Azure AD. If you need more features, like the one you mention, you must use Azure AD PowerShell. Azure AD PowerShell is not depricated and is the officially supported PowerShell module for working with Azure AD.
You can manage these required permissions by the Set-AzureAdApplication cmdlet and passing proper -RequiredResourceAccess object.
In order to construct this object, you must first get a reference to "exposed" permissions. Because permissions are exposed by other service principals.
as I cannot upload whole file, here is a PowerShell script that creates a sample application with required permission to some MS Graph and some Power BI permissions.
Function GetToken
{
param(
[String] $authority = "https://login.microsoftonline.com/dayzure.com/oauth2/token",
[String] $clientId,
[String] $clientSecret,
[String] $resourceId = "https://graph.windows.net"
)
$scope = [System.Web.HttpUtility]::UrlEncode($resourceId)
$encSecret = [System.Web.HttpUtility]::UrlEncode($clientSecret)
$body = "grant_type=client_credentials&resource=$($scope)&client_id=$($clientId)&client_secret=$($encSecret)"
$res = Invoke-WebRequest -Uri $authority -Body $body -Method Post
$authResult = $res.Content | ConvertFrom-Json
return $authResult.access_token
}
#`
# -RequiredResourceAccess #($requiredResourceAccess)
#
Function CreateChildApp
{
param (
[string] $displayName,
[string] $tenantName
)
# create your new application
Write-Output -InputObject ('Creating App Registration {0}' -f $displayName)
if (!(Get-AzureADApplication -SearchString $displayName)) {
$app = New-AzureADApplication -DisplayName $displayName `
-Homepage "https://localhost" `
-ReplyUrls "https://localhost" `
-IdentifierUris ('https://{0}/{1}' -f $tenantName, $displayName)
# create SPN for App Registration
Write-Output -InputObject ('Creating SPN for App Registration {0}' -f $displayName)
# create a password (spn key)
$appPwd = New-AzureADApplicationPasswordCredential -ObjectId $app.ObjectId
$appPwd
# create a service principal for your application
# you need this to be able to grant your application the required permission
$spForApp = New-AzureADServicePrincipal -AppId $app.AppId -PasswordCredentials #($appPwd)
}
else {
Write-Output -InputObject ('App Registration {0} already exists' -f $displayName)
$app = Get-AzureADApplication -SearchString $displayName
}
#endregion
return $app
}
Function GrantAllThePermissionsWeWant
{
param
(
[string] $targetServicePrincipalName,
$appPermissionsRequired,
$childApp,
$spForApp
)
$targetSp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$($targetServicePrincipalName)'"
# Iterate Permissions array
Write-Output -InputObject ('Retrieve Role Assignments objects')
$RoleAssignments = #()
Foreach ($AppPermission in $appPermissionsRequired) {
$RoleAssignment = $targetSp.AppRoles | Where-Object { $_.Value -eq $AppPermission}
$RoleAssignments += $RoleAssignment
}
$ResourceAccessObjects = New-Object 'System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]'
foreach ($RoleAssignment in $RoleAssignments) {
$resourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess"
$resourceAccess.Id = $RoleAssignment.Id
$resourceAccess.Type = 'Role'
$ResourceAccessObjects.Add($resourceAccess)
}
$requiredResourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess"
$requiredResourceAccess.ResourceAppId = $targetSp.AppId
$requiredResourceAccess.ResourceAccess = $ResourceAccessObjects
# set the required resource access
Set-AzureADApplication -ObjectId $childApp.ObjectId -RequiredResourceAccess $requiredResourceAccess
Start-Sleep -s 1
# grant the required resource access
foreach ($RoleAssignment in $RoleAssignments) {
Write-Output -InputObject ('Granting admin consent for App Role: {0}' -f $($RoleAssignment.Value))
New-AzureADServiceAppRoleAssignment -ObjectId $spForApp.ObjectId -Id $RoleAssignment.Id -PrincipalId $spForApp.ObjectId -ResourceId $targetSp.ObjectId
Start-Sleep -s 1
}
}
cls
#globaladminapp
$clientID = "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
$key = "****"
$tenantId = "aaaaaaaa-bbbb-xxxx-yyyy-aaaaaaaaaaaa";
$TenantName = "customdomain.com";
$AppRegName = "globaladminChild-0003";
$token = GetToken -clientId $clientID -clientSecret $key
Disconnect-AzureAD
Connect-AzureAD -AadAccessToken $token -AccountId $clientID -TenantId $tenantId
$appPermissionsRequired = #('Application.ReadWrite.OwnedBy', 'Device.ReadWrite.All', 'Domain.ReadWrite.All')
$targetServicePrincipalName = 'Windows Azure Active Directory'
#$appPermissionsRequired = #('Files.ReadWrite.All','Sites.FullControl.All','Notes.ReadWrite.All')
#$targetServicePrincipalName = 'Microsoft Graph'
$app = CreateChildApp -displayName $AppRegName -tenantName $TenantName
$spForApp = Get-AzureADServicePrincipal -Filter "DisplayName eq '$($AppRegName)'"
$appPermissionsRequired = #('Tenant.ReadWrite.All')
$targetServicePrincipalName = 'Power BI Service'
GrantAllThePermissionsWeWant -targetServicePrincipalName $targetServicePrincipalName -appPermissionsRequired $appPermissionsRequired -childApp $app -spForApp $spForApp
$appPermissionsRequired = #('Files.ReadWrite.All','Sites.FullControl.All','Notes.ReadWrite.All')
$targetServicePrincipalName = 'Microsoft Graph'
GrantAllThePermissionsWeWant -targetServicePrincipalName $targetServicePrincipalName -appPermissionsRequired $appPermissionsRequired -childApp $app -spForApp $spForApp
The interesting parts are around "apppermissionrequired" and "targetserviceprincipalname" variables.
I can't reply to Rolfo's comment directly as I don't have enough clout yet. While it's true it's not dead simple, it's possible to use both in the same session as of July 2021. Not sure this was always the case, or something was updated to allow it.
#Import modules if needed
$mList = #("AzureAD","Az.Resources","Az.Accounts")
foreach($m in $mList){if ((gmo -l $m).Count -eq 0){Install-Module -Name $m -AllowClobber -Scope CurrentUser -Force}}
#Authentication Popup
Connect-AzAccount
#Use authentication context cached from above to authenticate to AAD graph
$IDObject = Get-AzAccessToken -Resource "https://graph.windows.net"
Connect-AzureAD -AadAccessToken $IDObject.token -AccountId $IDObject.UserId
UPDATE
With the new Graph API we can use the following command to add API permissions to an App Registration/Service Principal using PowerShell. It's much simpler than the old process.
Add-AzADAppPermission -ApplicationId "$spId" -ApiId "00000009-0000-0000-c000-000000000000" -PermissionId "7504609f-c495-4c64-8542-686125a5a36f"
(This is the case for the PowerBI API)
If deploying via an Azure Devops Pipeline I often recommend using the following script to authenticate into AAD:
echo "Install Azure AD module..."
Install-Module -Name "AzureAD" -Force
Import-Module AzureAD -Force
echo "Connect Azure AD..."
$context = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile.DefaultContext
echo $context
$graphToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.microsoft.com").AccessToken
echo $graphToken
$aadToken = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate($context.Account, $context.Environment, $context.Tenant.Id.ToString(), $null, [Microsoft.Azure.Commands.Common.Authentication.ShowDialog]::Never, $null, "https://graph.windows.net").AccessToken
Write-Output "Hi I'm $($context.Account.Id)"
Connect-AzureAD -AadAccessToken $aadToken -AccountId $context.Account.Id -TenantId $context.tenant.id -MsAccessToken $graphToken
echo "Connection ends"

Start-AzureSqlDatabaseExport Object Reference not set to an instance of an object

I'm not sure how to debug this, assuming it's not a problem with the cmdlet. I'm trying to replace the automated SQL export with an automation workflow, but I can't seem to get Start-AzureSqlDatabaseExport to work -- it keeps getting the following warning and error messages.
d4fc0004-0c0b-443e-ad1b-310af7fd4e2a:[localhost]:Client Session Id: 'c12c92eb-acd5-424d-97dc-84c4e9c4f914-2017-01-04
19:00:23Z'
d4fc0004-0c0b-443e-ad1b-310af7fd4e2a:[localhost]:Client Request Id: 'd534f5fd-0fc0-4d68-8176-7508b35aa9d8-2017-01-04
19:00:33Z'
Start-AzureSqlDatabaseExport : Object reference not set to an instance of an object.
At DBBackup:11 char:11
+
+ CategoryInfo : NotSpecified: (:) [Start-AzureSqlDatabaseExport], NullReferenceException
+ FullyQualifiedErrorId : Microsoft.WindowsAzure.Commands.SqlDatabase.Database.Cmdlet.StartAzureSqlDatabaseExport
This seems similar to some other questions, but they seem to be unanswered or not applicable. I did have a similar procedure working in the Powershell environment. I replaced that procedure with the automated export from Azure, which seems like a poor choice now! I've tried a number of variations, using sqlcontext and databasename instead of database, for example.
Here's my code with sensitive parts replaced with ****:
workflow DBBackup {
param(
[parameter(Mandatory=$true)]
[string] $dbcode
)
$cred = Get-AutomationPSCredential -Name "admindbcredentials"
$VerbosePreference = "Continue"
inlineScript {
$dbcode = $using:dbcode
$cred = $using:cred
if ($dbcode -eq $null)
{
Write-Output "Database code must be specified"
}
Else
{
$dbcode = $dbcode.ToUpper()
$dbsize = 1
$dbrestorewait = 10
$dbserver = "kl8p7d444a"
$stacct = $dbcode.ToLower()
$stkey = "***storagekey***"
Write-Verbose "DB Server '$dbserver' DB Code '$dbcode'"
Write-Verbose "Storage Account '$stacct'"
$url = "https://$dbserver.database.windows.net"
$sqlctx = New-AzureSqlDatabaseServerContext -ManageUrl $url -Credential $cred
# $sqlctx = New-AzureSqlDatabaseServerContext -ManageUrl $url -Credential $cred
$stctx = New-AzureStorageContext -StorageAccountName $stacct -StorageAccountKey $stkey
$dbname = "FSUMS_" + $dbcode
$dt = Get-Date
$timestamp = $dt.ToString("yyyyMMdd") + "_" + $dt.ToString("HHmmss")
$bkupname = $dbname + "_" + $timestamp + ".bacpac"
$stcon = Get-AzureStorageContainer -Context $stctx -Name "backups"
$db = Get-AzureSqlDatabase -Context $sqlctx -DatabaseName $dbname
Write-Verbose "Backup $dbname to $bkupname in storage account $stacct"
Start-AzureSqlDatabaseExport $sqlctx -DatabaseName $dbname -StorageContainer $stcon -BlobName $bkupname
}
}
}
Try New-AzureRmSqlDatabaseExport instead. This command will return export status object. If you want a synchronous export you can check for "export status" in a loop.
Adding the following lines corrected the problem:
In the workflow before inlineScript:
$cred = Get-AutomationPSCredential -Name "admincredentials"
(where admincredentials was an asset with my admin login credentials)
and inside the inlineScript:
Add-AzureAccount $cred
Select-AzureSubscription "My subscription"
Some runbooks don't seem to need this, but probably best to always include it.

Resources