Thursday, June 6, 2019

Grant permissions and trust SharePoint app automatically via PowerShell

In Sharepoint app model we may need to grant permissions to Sharepoint app on AppInv.aspx page by providing appropriate permissions request xml. If permissions are granted on Tenant level you need to open AppInv.aspx in context of Central admin i.e. https://{tenant}-admin.sharepoint.com:

It was historically quite painful to automate this process as automatic permissions grant is not currently possible. There were attempts to automate O365 login and automate trust process using COM automation in PowerShell (using New-Object -com internetexplorer.application): https://github.com/wulfland/ScriptRepository/blob/master/Apps/Apps/Deploy-SPApp.ps1. With this approach script opens AppInv.aspx page and simulates user’s input.

However O365 login experience was changed since this script was implemented and there is no guarantee that it won’t be changed further. Also there may be several login scenarios:

  • user may be already logged in if chose Remember credentials during previous login
  • user may use MFA with SMS, authenticator app or something else which will make login automation even more complicated

Keeping that in mind I implemented the following semi-automatic way of granting app permissions and trust the app:

1. app is registered in Azure AD via PowerShell (in Sharepoint Online it is not necessary to register app which will be used for communicating with Sharepoint via AppRegNew.aspx. You may also register it in Azure Portal > App Registrations). See e.g. Create an Azure Active Directory Application and Key using PowerShell for example

2. Then script opens AppInv.aspx page in IE (using Start-Process cmdlet) and asks user to authenticate him/herself manually. After that user returns to the script and clicks Enter – all other steps (grant permissions and trust the app) are performed by the following PowerShell script:

function Trust-SPAddIn {
    [CmdletBinding(SupportsShouldProcess=$true)]
    [OutputType([int])]
    Param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [string]$AppInstanceId,

        [Parameter(Mandatory=$true, Position=1)]
        [string]$WebUrl,

        [parameter(Mandatory=$true, Position=2)] 
        [string]$UserName, 

        [parameter(Mandatory=$true, Position=3)] 
        [string]$Password
    )

    $ie = New-Object -com internetexplorer.application
    try {
  Log-Warn ("Script will now open $WebUrl. Please authenticate yourself and wait until Admin Center home page will be loaded.")
  Log-Warn ("After that leave Admin Center window opened (don't close it), return to the script and follow provided instructions.")
  Log-Warn ("In case you are already signed in Admin Center window will be opened without asking to login. In this case wait until Admin Center window will be loaded, leave it opened and return to the script.")
  if (-not $silently) {
   Log-Warn ("Press Enter to open $WebUrl...")
   Read-Host
  }
 
        $ie.Visible = $true
        $ie.Navigate2($WebUrl)
  
  if (-not $silently) {
   Log-Warn ("Wait until Admin Center window will be fully loaded and press Enter to continue installation")
   Log-Warn ("Don't close Admin Center window - script will close it automatically")
   Read-Host
  }
  
  $authorizeURL = "$($WebUrl.TrimEnd('/'))/_layouts/15/appinv.aspx"
  Log-Info ("Open $authorizeURL...")
  $ie.Visible = $false
  $ie.Navigate2($authorizeURL)
  WaitFor-IEReady $ie -initialWaitInSeconds 3

  Log-Info ("Grant permissions to the app...")
  $appIdInput = $ie.Document.getElementById("ctl00_ctl00_PlaceHolderContentArea_PlaceHolderMain_IdTitleEditableInputFormSection_ctl01_TxtAppId")
  $appIdInput.value = $AppInstanceId
  $lookupBtn = $ie.Document.getElementById("ctl00_ctl00_PlaceHolderContentArea_PlaceHolderMain_IdTitleEditableInputFormSection_ctl01_BtnLookup")
  $lookupBtn.Click()
  WaitFor-IEReady $ie -initialWaitInSeconds 3
  Log-Info ("Step 1 of 2 done")
  $appIdInput = $ie.Document.getElementById("ctl00_ctl00_PlaceHolderContentArea_PlaceHolderMain_TitleDescSection_ctl01_TxtPerm")
  $appIdInput.value = '<AppPermissionRequests AllowAppOnlyPolicy="true"><AppPermissionRequest Scope="http://sharepoint/content/tenant" Right="FullControl" /></AppPermissionRequests>'
  $createBtn = $ie.Document.getElementById("ctl00_ctl00_PlaceHolderContentArea_PlaceHolderMain_ctl01_RptControls_BtnCreate")
  $createBtn.Click()
  WaitFor-IEReady $ie -initialWaitInSeconds 3
  Log-Info ("Step 2 of 2 done")

  Log-Info ("Trust the app...")
  $trustBtn = $ie.Document.getElementById("ctl00_ctl00_PlaceHolderContentArea_PlaceHolderMain_BtnAllow")
  $trustBtn.Click()
  WaitFor-IEReady $ie -initialWaitInSeconds 3

  Log-Info ("All steps are done")
    }
    finally {
        $ie.Quit()
    } 
}

WaitFor-IEReady helper method is given from original script mentioned above so credits go to it’s author:

function WaitFor-IEReady {
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)]
        $ie,

        [Parameter(Mandatory=$false, Position=1)]
        $initialWaitInSeconds = 1
    )

    sleep -Seconds $initialWaitInSeconds

    while ($ie.Busy) {

        sleep -milliseconds 50
    }
}

Log-Info and Log-Warn are basic logger methods and you may implement them as needed for your scenario. Since we delegated login to the end user we don’t need to handle different O365 login scenarios and script is greatly simplified, e.g. there is no need to perform javascript activities which work not very stable via COM automation.

No comments:

Post a Comment