Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/microsoft/BCTech
Browse files Browse the repository at this point in the history
  • Loading branch information
KennieNP committed May 9, 2020
2 parents fd02c28 + 9f5325e commit 6029e94
Show file tree
Hide file tree
Showing 11 changed files with 456 additions and 0 deletions.
77 changes: 77 additions & 0 deletions samples/AdminCenterApi/Authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Authenticating to the Business Central Admin Center API

A main obstacle when calling APIs is how to authenticate.

The Admin Center API is based on Azure Active Directory (AAD) authentication using the OAuth standard. OAuth can seem complicated at first, but once you understand the basics, it is easy to use and actually makes life much easier - especially when calling across AAD tenants.

On this page, we explain a few simple steps that will get you up and running quickly.



## Register a new Azure Active Directory (AAD) Application in your AAD Tenant

1. Open https://portal.azure.com
2. Register new application:
- Click "App registrations"
- Click "New registration"
- Give a name such as "App for managing Business Central environments"
- Select "Accounts in any organizational directory (Any Azure AD directory - Multitenant)"
- Add a redirect URI: "Public client/native" and "nativeBusinessCentralClient://auth"
- Click "Register"
3. Create the AAD application's service principal:
- On the AAD application's "Overview" page, locate the "Managed application in local directory" property
- If the value is a link called "Create Service Principal", then click that link
- Navigate back in the browser to get back to the AAD application's "Overview" page
4. Make a note of the "Application (client) ID"
5. Add permission to call Business Central APIs:
- Click "API permissions"
- Click "Add a permission"
- Click "Microsoft APIs"
- Click "Dynamics 365 Business Central"
- Click "Delegated permissions"
- Select "user_impersonation"
- Click "Add permissions"


**At this point**, the AAD application has been registered in your (the partner's) AAD tenant, and you should know these two values:

$aadAppId = "<a guid>"
$aadAppRedirectUri = "nativeBusinessCentralClient://auth"



## Specifically for Delegated Admins

This section only applies to partners who have a reseller relationship with customers, also known as Delegated Admins.

As a delegated admin, you want to manage your customers' Business Central environments. From an authentication perspective, this is significantly different from managing your own Business Central environments.

In order to call Business Central APIs for a customer, you need to authenticate **in the customer's AAD tenant**, not in your own AAD tenant. This process normally requires an administrator of the customer's AAD tenant to give consent to your AAD application, however, this step can be avoided for delegated admins by adding your AAD application into the Admin Agents group in your own AAD tenant. This technique is described in more detail here: https://docs.microsoft.com/graph/auth-cloudsolutionprovider.

In short, you need to execute the following PowerShell commands (remember to set the $aadAppId variable):

Install-Module AzureAD
Connect-AzureAD
$adminAgentsGroup = Get-AzureADGroup -Filter "displayName eq 'AdminAgents'"
$servicePrincipal = Get-AzureADServicePrincipal -Filter "appId eq '$aadAppId'"
Add-AzureADGroupMember -ObjectId $adminAgentsGroup.ObjectId -RefObjectId $servicePrincipal.ObjectId

Now it's all set up, and you should be able to use your AAD application (in your AAD tenant) to call
into your customers' Business Central environments (in their AAD tenants).



## Obtain an Access Token

You will use your newly registered AAD application to obtain a so-called *access token*, which is a long string that looks something like this:

eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkN0VHVoTUptRDVNN0RMZHpEMnYyeDNRS1NSWSIsImtpZCI6IkN0VHVoTUptRDVNN0RMZHpEMnYyeDNRS1NSWSJ9.eyJhdWQiOiJodHRwIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2...yMTc3MTQ1ZTEwIl19.LZgQnXOLNNpJgBx5q7FOUgq5ka04lJkBw75kxMTUA7hFDEL-NsMVcwQ_Zt-H0aPkOevCAQ_KWtZRQA

This string encodes different information, including who you are, and which API you want to call. You don't need to know all about access tokens - the main thing to know is that in order to use it, you need to send it in the Authorization header of each HTTP request, like this:

GET https://api.businesscentral.dynamics.com/admin/v2.1/applications/businesscentral/environments
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6IkN0VHVoTUptRDVNN0RMZHpEMnYyeDNRS1NSWSIsImtpZCI6IkN0VHVoTUptRDVNN0RMZHpEMnYyeDNRS1NSWSJ9.eyJhdWQiOiJodHRwIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2...yMTc3MTQ1ZTEwIl19.LZgQnXOLNNpJgBx5q7FOUgq5ka04lJkBw75kxMTUA7hFDEL-NsMVcwQ_Zt-H0aPkOevCAQ_KWtZRQA

To obtain an access token in PowerShell, you can follow the steps in the [Authenticate.ps1](PowerShell/Authenticate.ps1) script.

And to use the access token to call the Admin Center API, you can follow the steps in e.g. the [Environments.ps1](PowerShell/Environments.ps1) script.
Empty file.
53 changes: 53 additions & 0 deletions samples/AdminCenterApi/PowerShell/Apps.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# This file contains examples of API calls that can be used to manage apps for an environment.


# Shared Parameters
$environmentName = "MyProd"
#$accessToken = "" # get this from the Authenticate sample


# List installed apps
Write-Host -ForegroundColor Cyan "Listing installed apps in environment $environmentName for a customer..."
$response = Invoke-WebRequest `
-Method Get `
-Uri "https://api.businesscentral.dynamics.com/admin/v2.1/applications/businesscentral/environments/$environmentName/apps" `
-Headers @{Authorization=("Bearer $accessToken")}
$installedApps = ConvertTo-Json (ConvertFrom-Json $response.Content) # prettify json
Write-Host $installedApps


# Get available updates
Write-Host -ForegroundColor Cyan "Getting available app updates for environment $environmentName for a customer..."
$response= Invoke-WebRequest `
-Method Get `
-Uri "https://api.businesscentral.dynamics.com/admin/v2.1/applications/businesscentral/environments/$environmentName/apps/availableUpdates" `
-Headers @{Authorization=("Bearer $accessToken")}
$availableUpdates = ConvertTo-Json (ConvertFrom-Json $response.Content) # prettify json
Write-Host $availableUpdates


# Update one of the apps
$appIdToUpdate = "63ca2fa4-4f03-4f2b-a480-172fef340d3f"
$appTargetVersion = "16.0.11240.12736"
Write-Host -ForegroundColor Cyan "Scheduling an update of app $appIdToUpdate in environment $environmentName..."
$response= Invoke-WebRequest `
-Method Post `
-Uri "https://api.businesscentral.dynamics.com/admin/v2.1/applications/businesscentral/environments/$environmentName/apps/$appIdToUpdate/update" `
-Body (@{
targetVersion = $appTargetVersion
} | ConvertTo-Json) `
-Headers @{Authorization=("Bearer $accessToken")} `
-ContentType "application/json"
$operationStatus = ConvertTo-Json (ConvertFrom-Json $response.Content) # prettify json
Write-Host $operationStatus


# Check update status
Write-Host -ForegroundColor Cyan "Checking status on the update operation..."
$response= Invoke-WebRequest `
-Method Get `
-Uri "https://api.businesscentral.dynamics.com/admin/v2.1/applications/businesscentral/environments/$environmentName/apps/$appIdToUpdate/operations" `
-Headers @{Authorization=("Bearer $accessToken")}
$operations = ConvertTo-Json (ConvertFrom-Json $response.Content) # prettify json
Write-Host $operations

29 changes: 29 additions & 0 deletions samples/AdminCenterApi/PowerShell/Authenticate.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# This sample authenticates to Azure Active Directory (AAD) an obtains an access token.
# The access token can be used for authenticating to Business Central APIs.


# Parameters
$identityClientPath = "C:\Program Files\WindowsPowerShell\Modules\AzureAD\2.0.2.76\Microsoft.IdentityModel.Clients.ActiveDirectory.dll" # Install-Module AzureAD to get this
$aadTenantId = "14ae87bb-40fe-434a-b185-8eea9645e857" # customer's tenant id
$aadAppId = "a19cb26a-2e4c-408b-82e1-6311742ecc50" # partner's AAD app id
$aadAppRedirectUri = "nativeBusinessCentralClient://auth" # partner's AAD app redirect URI


# Load Microsoft.IdentityModel.Clients.ActiveDirectory.dll
Add-Type -Path $identityClientPath


# Get access token
$ctx = [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext]::new("https://login.microsoftonline.com/$aadTenantId")
$redirectUri = New-Object -TypeName System.Uri -ArgumentList $aadAppRedirectUri
$platformParameters = New-Object -TypeName Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters -ArgumentList ([Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Always)
$accessToken = $ctx.AcquireTokenAsync("https://api.businesscentral.dynamics.com", $aadAppId, $redirectUri, $platformParameters).GetAwaiter().GetResult().AccessToken


# Print access token
Write-Host (ConvertTo-Json (ConvertFrom-Json ([System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($accessToken.Split('.')[1])))) -Depth 99)
Write-Host -ForegroundColor Cyan 'Authentication complete - we have an access token for Business Central, and it is stored in the $accessToken variable.'


# Extract the AAD tenant id from the access token. If you ever have an access token, and you want to know in which customer AAD tenant it is valid, you can execute this line:
$aadTenantId = (ConvertTo-Json (ConvertFrom-Json ([System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($accessToken.Split('.')[1])))).tid)
13 changes: 13 additions & 0 deletions samples/AdminCenterApi/PowerShell/DatabaseExports.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# TODO


# How to get a storage account SAS URL:
# TODO


# Export environment to a .bacpac file in a storage account
# TODO


# Check export history
# TODO
83 changes: 83 additions & 0 deletions samples/AdminCenterApi/PowerShell/Environments.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# This file contains examples of API calls that can be used to manage environments for a customer.
# WARNING: the sample will modify some environments; execute this code at your own risk.


# Shared Parameters
#$accessToken = "" # get this from the Authenticate sample


# Get list of environments
Write-Host -ForegroundColor Cyan "Listing environments for a customer..."
$response = Invoke-WebRequest `
-Method Get `
-Uri "https://api.businesscentral.dynamics.com/admin/v2.1/applications/businesscentral/environments" `
-Headers @{Authorization=("Bearer $accessToken")}
$environments = ConvertTo-Json (ConvertFrom-Json $response.Content) # prettify json
Write-Host $environments


# Set AppInsights key
$environmentName = "MyProd"
$newAppInsightsKey = "00000000-1111-2222-3333-444444444444"
$response = Invoke-WebRequest `
-Method Post `
-Uri "https://api.businesscentral.dynamics.com/admin/v2.1/applications/businesscentral/environments/$environmentName/settings/appinsightskey" `
-Body (@{
key = $newAppInsightsKey
} | ConvertTo-Json) `
-Headers @{Authorization=("Bearer $accessToken")} `
-ContentType "application/json"
Write-Host "Responded with: $($response.StatusCode) $($response.StatusDescription)"


# Get update window
$environmentName = "MyProd"
$response = Invoke-WebRequest `
-Method Get `
-Uri "https://api.businesscentral.dynamics.com/admin/v2.1/applications/businesscentral/environments/$environmentName/settings/upgrade" `
-Headers @{Authorization=("Bearer $accessToken")}
Write-Host "Update window: $(ConvertTo-Json (ConvertFrom-Json $response.Content) )"


# set update window
$environmentName = "MyProd"
$preferredStartTimeUtc = "2000-01-01T02:00:00Z"
$preferredEndTimeUtc = "2000-01-01T09:00:00Z"
$response = Invoke-WebRequest `
-Method Put `
-Uri "https://api.businesscentral.dynamics.com/admin/v2.1/applications/businesscentral/environments/$environmentName/settings/upgrade" `
-Body (@{
preferredStartTimeUtc = $preferredStartTimeUtc
preferredEndTimeUtc = $preferredEndTimeUtc
} | ConvertTo-Json) `
-Headers @{Authorization=("Bearer $accessToken")} `
-ContentType "application/json"


# Create new environment
$newEnvironmentName = "MyNewSandbox"
$response = Invoke-WebRequest `
-Method Put `
-Uri "https://api.businesscentral.dynamics.com/admin/v2.1/applications/businesscentral/environments/$newEnvironmentName" `
-Body (@{
EnvironmentType = "Sandbox"
CountryCode = "DK"
} | ConvertTo-Json) `
-Headers @{Authorization=("Bearer $accessToken")} `
-ContentType "application/json"


# Copy production environment to a sandbox environment
$environmentName = "MyProd"
$newEnvironmentName = "MyNewSandboxAsACopy"
$response = Invoke-WebRequest `
-Method Put `
-Uri "https://api.businesscentral.dynamics.com/admin/v2.1/applications/businesscentral/environments/$newEnvironmentName" `
-Body (@{
EnvironmentType = "Sandbox"
CountryCode = "DK"
copyFromEnvironmentName = $environmentName
} | ConvertTo-Json) `
-Headers @{Authorization=("Bearer $accessToken")} `
-ContentType "application/json"

3 changes: 3 additions & 0 deletions samples/AdminCenterApi/PowerShell/NotificationRecipients.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# This file contains examples of API calls that can be used to manage the notification recipients for a customer.

TODO
30 changes: 30 additions & 0 deletions samples/AdminCenterApi/PowerShell/PartnerCenter.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This sample is relevant for partners who are registered as resellers for one or more customers.
# It allows the partner to perform Partner Center operations programmatically, e.g., to enumerate customers,
# see their subscriptions, etc.


Install-Module PartnerCenter # only needs to be done once, see documentation at https://docs.microsoft.com/powershell/module/partnercenter


# Authenticate to Partner Center
Connect-PartnerCenter


# Get all customers
$customers = Get-PartnerCustomer
Write-Host "Found $($customers.Length) customers."


# Filter down the list for demo purposes
$customers = $customers | ? -Property Name -Like "*MS Virtual Event*"
Write-Host "Will work with these $($customers.Length) customers going forward:"
$customers | Format-Table


# Get subscriptions
foreach ($customer in $customers)
{
Write-Host "$($customer.CustomerId), $($customer.Domain), $($customer.Name) has the following subscriptions:"
$subscriptions = Get-PartnerCustomerSubscription -InputObject $customer
$subscriptions | Select-Object -Property Status,Quantity,FriendlyName,IsTrial,CommitmentEndDate,AutoRenewEnabled | Format-Table
}
Loading

0 comments on commit 6029e94

Please sign in to comment.