Unlocking the Hidden Microsoft Forms API with PowerShell & Azure App Registration

Run fully automated scripts with application permissions to pull Microsoft Forms data, as if it were an official Graph API — without any extra licenses required

For years, there’s been no official Microsoft Forms API, despite countless requests from admins and developers who need to automate data collection, reporting, or integration workflows. While Microsoft Graph covers most services, Forms has remained an outlier — its API exists and is used by the Forms web interface, but it’s undocumented, making it tricky to use for automation.

Historically, if you wanted automation around Microsoft Forms, you were pushed towards Power Automate, PowerApps, or Power BI. Those tools are powerful, but they often require additional licenses and can introduce costs that aren’t feasible for every project.

The approach I’m sharing here avoids that problem: it uses application permissions via an Azure App Registration. That means scripts can run fully automated in the background without user interaction, pulling data straight from the undocumented Forms API — essentially treating it like an official Graph endpoint.

This post walks you through the approach, shows the reusable PowerShell code, and highlights how you can start automating your own Forms workflows.

Update (13/10/2025):

The group endpoints in the Microsoft Forms API only work with delegated permissions. This means you must authenticate using the ROPC (Resource Owner Password Credentials) flow rather than client credentials.

To use the ROPC flow, the account you authenticate with must be excluded from MFA (multi-factor authentication), as the flow cannot handle MFA prompts. It is recommended to use a long, secure password for this account. You’ll only be able to access forms owned by groups that the authenticated account is a member of.

The TokenManager class has been updated to support this flow, allowing tokens to be obtained via ROPC when needed for group-level requests. I have also included a Groups TokenManager example under the “Get All Forms by a Group” section, demonstrating how to authenticate and retrieve forms for groups the account belongs to.

Create an App Registration in Azure

First, we need an Azure AD App Registration with permission to read Forms.

  1. Go to Azure Portal → App Registrations → New registration.
  2. Give it a name, register it as Accounts in this organizational directory only.
  3. Under API Permissions → Add a permission → APIs my organization uses, search for Microsoft Forms.
  4. Select Application permissions → Forms.Read.All (for group endpoints use Delegated permissions → Forms.Read)
  5. Grant admin consent
  6. Go to Certificates & secrets → New client secret, and note the secret value.

As we’re using application permissions, this app can run completely headless — no sign-in prompts, no delegated user context. Perfect for automation.

Next, we’ll dive into the PowerShell code that makes it all work, including the reusable functions and token manager to handle authentication automatically.

Token Management

TInstead of getting a new token for every request, I created a TokenManager class in PowerShell:

  • Fetches a new token if none exists.
  • Refreshes only when the token is expired or within 60 seconds of expiry.
  • Stores the token for reuse across multiple API calls in the same session.

Some might say this is overkill for a few quick calls; but if you’re working with a form that has thousands of responses, the script could run for hours as it pages through responses. In that case, the Token Manager quietly refreshes tokens in the background so your script stays authorized the whole time — no manual intervention needed.

PowerShell
class TokenManager {
    [string]$TenantId
    [string]$ClientId
    [string]$ClientSecret
    [string]$Username
    [string]$Pass
    [string]$Scope
    [string]$AccessToken
    [datetime]$Expiry
    [string]$TokenEndpoint
    [bool]$RopcFlow = $false

    TokenManager([string]$Username, [string]$Pass, [string]$TenantId, [string]$ClientId, [string]$ClientSecret, [string]$Scope) {
        $this.Username = $Username
        $this.Pass = $Pass
        $this.ClientId = $ClientId
        $this.ClientSecret = $ClientSecret
        $this.Scope = $Scope
        $this.TokenEndpoint = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
        $this.RopcFlow = $true
    }

    TokenManager([string]$TenantId, [string]$ClientId, [string]$ClientSecret, [string]$Scope) {
        $this.TenantId = $TenantId
        $this.ClientId = $ClientId
        $this.ClientSecret = $ClientSecret
        $this.Scope = $Scope
        $this.TokenEndpoint = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
    }

    [string]GetToken() {
        if (-not $this.AccessToken -or (Get-Date) -ge $this.Expiry) {
            $this.RefreshToken()
        }

        return $this.AccessToken
    }

    [void]RefreshToken() {
        $body = [ordered]@{
            client_id     = $this.ClientId
            client_secret = $this.ClientSecret
            grant_type    = "client_credentials"
            scope         = $this.Scope
        }

        if ($this.RopcFlow) {
            $body["username"] = $this.Username
            $body["password"] = $this.Pass
            $body["grant_type"] = "password"
        }

        $tokenResponse = Invoke-RestMethod -Uri $this.TokenEndpoint -Method Post -Body $body -ContentType "application/x-www-form-urlencoded"

        $this.AccessToken = $tokenResponse.access_token

        $this.Expiry = (Get-Date).AddSeconds($tokenResponse.expires_in - 60)

        Write-Verbose "Access token refreshed. Expires $($this.Expiry)."
    }
}

Authenticated HTTP Requests

To make calling the API easier, I wrote a wrapper function that:

  • Automatically attaches the Bearer token
  • Retries on 401 errors (token expiry)
  • Supports all HTTP verbs

This means the rest of the script doesn’t need to care about authentication — it just calls Invoke-HttpRequestWithToken and gets JSON back.

PowerShell
function Invoke-HttpRequestWithToken {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [ValidateSet("GET", "POST", "PUT", "PATCH", "DELETE")]
        [string]$Method,

        [Parameter(Mandatory = $true)]
        [ValidateScript({ ![string]::IsNullOrWhiteSpace($_) })]
        [string]$Uri,

        [Parameter(Mandatory = $false)]
        [hashtable]$Headers = @{},

        [Parameter(Mandatory = $false)]
        [hashtable]$Body,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [TokenManager]$TokenManager,

        [Parameter(Mandatory = $false)]
        [int]$MaxRetries = 5
    )

    begin {
        $output = $null
        $retry = 0
        $Headers.Authorization = "Bearer $($TokenManager.GetToken())"
    }

    process {
        while ($retry -le $MaxRetries) {
            try {
                Write-Verbose "Making $Method request to $Uri (attempt $($retry+1))"

                if ($PSBoundParameters.ContainsKey("Body")) {
                    $output = Invoke-RestMethod -Uri $Uri -Method $Method -Headers $Headers -Body ($Body | ConvertTo-Json) -ContentType "application/json"

                    return
                }
                else {
                    $output = Invoke-RestMethod -Uri $Uri -Method $Method -Headers $Headers

                    return
                }
            }
            catch [System.Net.WebException] {
                if ($_.Exception.Response.StatusCode -eq 401 -and $retry -lt $MaxRetries) {
                    Write-Verbose "Token expired. Refreshing token..."

                    $TokenManager.RefreshToken();

                    $retry++
                    continue
                }
                else {
                    throw "HTTP request failed: $($_.Exception.Message)"
                }
            }
        }

        throw "Maximum retries ($MaxRetries) exceeded for $Method $Uri"
    }

    end {
        return $output
    }
}

Fetching Forms + Responses

Now let’s look at the Get-MSForm* functions that actually retrieve data from Microsoft Forms.

Get-MSFormsByOwner

This function retrieves all the forms owned by a given user or group. It fetches metadata such as:

  • Row count (number of responses)
  • Form title
  • Creation and modification dates
  • Status
PowerShell
function Get-MSFormsByOwner {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateScript({ ![string]::IsNullOrWhiteSpace($_) })]
        [string]$TenantId,

        [Parameter(Mandatory = $true)]
        [ValidateScript({ ![string]::IsNullOrWhiteSpace($_) })]
        [string]$OwnerId,

        [Parameter(Mandatory = $true)]
        [ValidateSet("User", "Group")]
        [string]$OwnerType,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [TokenManager]$TokenManager
    )

    begin {
        $output = New-Object System.Collections.Generic.List[PSCustomObject]

        $ownerContext = switch ($OwnerType) {
            "User" { "users" }
            "Group" { "groups" }
        }

        $endpoint = "https://forms.office.com/formapi/api/$TenantId/$ownerContext/$OwnerId/light/forms?`$select=id,status,title,createdDate,modifiedDate,ownerId,version,softDeleted,type"
    }

    process {
        try {
            Write-Verbose "Fetching forms for user $UserId..."

            $response = Invoke-HttpRequestWithToken -Method "GET" -Uri $endpoint -TokenManager $TokenManager

            foreach ($form in $response.value) {
                $formEndpoint = "https://forms.office.com/formapi/api/$TenantId/$ownerContext/$OwnerId/light/forms('$($form.id)')?`$select=rowCount"

                Write-Verbose "Fetching rowCount for form '$($form.title)' ($($form.id))..."

                $formResponse = Invoke-HttpRequestWithToken -Method "GET" -Uri $formEndpoint -TokenManager $TokenManager

                $form | Add-Member -MemberType NoteProperty -Name rowCount -Value $formResponse.rowCount

                $output.Add([PSCustomObject]$form)
            }
        }
        catch {
            throw "Failed to get forms: $($_.Exception.Message)"
        }
    }

    end {
        return $output
    }
}
Get-MSFormResponse

This function retrieves a single response from a specific form, including data such as:

  • Row count (number of responses)
  • Form title
  • Creation and modification dates
  • Status
PowerShell
function Get-MSFormResponse {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateScript({ ![string]::IsNullOrWhiteSpace($_) })]
        [string]$TenantId,

        [Parameter(Mandatory = $true)]
        [ValidateScript({ ![string]::IsNullOrWhiteSpace($_) })]
        [string]$OwnerId,

        [Parameter(Mandatory = $true)]
        [ValidateSet("User", "Group")]
        [string]$OwnerType,

        [Parameter(Mandatory = $true)]
        [ValidateScript({ ![string]::IsNullOrWhiteSpace($_) })]
        [string]$FormId,

        [Parameter(Mandatory = $true)]
        [int]$ResponseId,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [TokenManager]$TokenManager
    )

    begin {
        $output = $null

        $ownerContext = switch ($OwnerType) {
            "User" { "users" }
            "Group" { "groups" }
        }
    }

    process {
        $responsesEndpoint = "https://forms.office.com/formapi/api/$TenantId/$ownerContext/$OwnerId/light/forms('$FormId')/responses?`$filter=id eq $ResponseId"

        try {
            Write-Verbose "Fetching response ID $ResponseId for form $FormId..."

            $response = Invoke-HttpRequestWithToken -Method "GET" -Uri $responsesEndpoint -TokenManager $TokenManager

            if ($response.value) {
                $output = [PSCustomObject]$response.value
            }
        }
        catch {
            throw "Failed to get response: $($_.Exception.Message)"
        }
    }

    end {
        return $output
    }
}
Get-MSFormResponses

This is the workhorse function. It retrieves all responses for a given form, with full support for paging.

Key features:

  • Takes -PageSize to control batch size
  • Takes -Skip to start from a given offset
  • Automatically loops through until all responses are fetched
  • Adds verbose logging so you can see progress
PowerShell
function Get-MSFormResponses {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateScript({ ![string]::IsNullOrWhiteSpace($_) })]
        [string]$TenantId,

        [Parameter(Mandatory = $true)]
        [ValidateScript({ ![string]::IsNullOrWhiteSpace($_) })]
        [string]$OwnerId,

        [Parameter(Mandatory = $true)]
        [ValidateSet("User", "Group")]
        [string]$OwnerType,

        [Parameter(Mandatory = $true)]
        [ValidateScript({ ![string]::IsNullOrWhiteSpace($_) })]
        [string]$FormId,

        [Parameter(Mandatory = $true)]
        [int]$PageSize,

        [Parameter(Mandatory = $false)]
        [int]$Skip = 0,

        [Parameter(Mandatory = $true)]
        [ValidateNotNull()]
        [TokenManager]$TokenManager
    )

    begin {
        $output = New-Object System.Collections.Generic.List[PSCustomObject]

        $ownerContext = switch ($OwnerType) {
            "User" { "users" }
            "Group" { "groups" }
        }

        $formEndpoint = "https://forms.office.com/formapi/api/$TenantId/$ownerContext/$OwnerId/light/forms('$FormId')?`$select=rowCount"
        $formResponse = Invoke-HttpRequestWithToken -Uri $formEndpoint -Method "GET" -TokenManager $TokenManager
        $totalResponses = $formResponse.rowCount

        $totalToFetch = [math]::Max(0, $totalResponses - $Skip)

        $itemsFetched = 0
        $itemsToSkip = $Skip

        Write-Verbose "Form has $totalResponses responses. Starting at Skip=$Skip, PageSize=$PageSize. Total to fetch: $totalToFetch."
    }

    process {
        while ($itemsToSkip -lt $totalResponses) {
            $responsesEndpoint = "https://forms.office.com/formapi/api/$TenantId/$ownerContext/$OwnerId/light/forms('$FormId')/responses?`$expand=comments&`$top=$PageSize&`$skip=$itemsToSkip&"

            try {
                $response = Invoke-HttpRequestWithToken -Method "GET" -Uri $responsesEndpoint -TokenManager $TokenManager

                if (-not $response.value -or $response.value.Count -eq 0) {
                    Write-Verbose "No more responses returned from API. Ending paging."
                    break
                }

                $response.value | ForEach-Object {
                    $output.Add([PSCustomObject]$_)
                }

                $itemsFetched += $response.value.Count
                $itemsToSkip += $response.value.Count

                Write-Verbose ("Fetched {0} responses this page; cumulative fetched {1}/{2}; overall position {3}/{4}" -f `
                        $response.value.Count, `
                        $itemsFetched, `
                        $totalToFetch, `
                        [math]::Min($itemsToSkip, $totalResponses), `
                        $totalResponses)

            }
            catch {
                throw "Failed to get responses: $($_.Exception.Message)"
            }
        }
    }

    end {
        return $output
    }
}

Usage Examples

Now that we’ve walked through the code and functions, let’s look at some practical usage scenarios. These examples show how you can start automating Microsoft Forms data collection right away.

Set Up TokenManager

Before calling any of the Get-MSForm* functions, you need to create a TokenManager instance.

This requires the three values from your App Registration in Azure AD:

  • Tenant ID → Found in your App Registration → Overview
  • Client ID → Found in your App Registration → Overview
  • Client Secret → Generated in App Registration → Certificates & secrets
PowerShell
$TenantId     = "<your tenant id>"
$ClientId     = "<your client id>"
$ClientSecret = "<your client secret>"

$TokenManager = [TokenManager]::new(
    $TenantId,
    $ClientId,
    $ClientSecret,
    "https://forms.office.com/.default"
)

# Test by fetching a token
$TokenManager.GetToken()

If the token is valid, you’ll get a long string back. You don’t normally need to call GetToken() directly — the wrapper functions handle that for you — but it’s a good sanity check that your App Registration and permissions are working.

Get All Forms by a User

Retrieve a list of all forms owned by a specific user (identified by their Entra Object ID).

PowerShell
Get-MSFormsByOwner `
    -TenantId $TenantId `
    -OwnerId "<entra object id>" `
    -OwnerType "User" `
    -TokenManager $TokenManager

This gives you an inventory of forms, complete with how many responses each has.

Get All Forms by a Group

Retrieve a list of all forms owned by a specific group (identified by their Entra Object ID).

The Microsoft Forms group endpoints require delegated permissions (Forms.Read) and must be accessed using the ROPC flow rather than client credentials. The account used for ROPC must be excluded from MFA, and it is recommended to use a long, secure password for this account. You’ll only be able to access forms owned by groups that the authenticated account belongs to. To support this, you will need to instantiate the TokenManager using a different constructor, for example:

PowerShell
$TenantId = "<tenant id>"
$ClientId = "<client id>"
$ClientSecret = "<client secret>"
$Username = "[email protected]"
$Pass = "k\^e+yftk)e}5@!G`%ng"
$Scope = "https://forms.office.com/.default"

$TokenManager = [TokenManager]::new($Username, $Pass, $TenantId, $ClientId, $ClientSecret, $Scope)
PowerShell
Get-MSFormsByOwner `
    -TenantId $TenantId `
    -OwnerId "<entra object id>" `
    -OwnerType "Group" `
    -TokenManager $TokenManager

This gives you an inventory of forms, complete with how many responses each has.

Get a Single Response

Fetch one response by ID for a given form.

PowerShell
Get-MSFormResponse `
    -TenantId $TenantId `
    -OwnerId "<entra object id>" `
    -OwnerType "User" `
    -FormId "<form id>" `
    -ResponseId 1 `
    -TokenManager $TokenManager

Useful for debugging or verifying individual submissions.

Get All Responses with Paging

Fetch every response from a form in batches.

PowerShell
Get-MSFormResponses `
    -TenantId $TenantId `
    -OwnerId "<entra object id>" `
    -OwnerType "User" `
    -FormId "<form id>" `
    -PageSize 100 `
    -TokenManager $TokenManager

The PageSize parameter controls how many records are fetched per API call. This will loop automatically until all responses are retrieved — perfect for large surveys with thousands of entries.

Get Responses with Paging and Skip (Offset)

Sometimes you may only want to retrieve newer responses or continue where a previous export left off. The -Skip parameter lets you offset results.

For example, if you already exported the first 500 responses, you can skip them and fetch the rest:

PowerShell
# Get responses starting from response 501
Get-MSFormResponses `
    -TenantId $TenantId `
    -OwnerId"<entra object id>" `
    -OwnerType "User" `
    -FormId "<form id>" `
    -PageSize 100 `
    -Skip 500 `
    -TokenManager $TokenManager

This way, you don’t need to re-fetch thousands of old responses — only the new ones that came in since your last run. Perfect for incremental automation jobs.

Get Responses with Paging and Skip (Offset)

Sometimes you may only want to retrieve newer responses or continue where a previous export left off. The -Skip parameter lets you offset results.

For example, if you already exported the first 500 responses, you can skip them and fetch the rest:

PowerShell
# Get responses starting from response 501
Get-MSFormResponses `
    -TenantId $TenantId `
    -OwnerId "<entra object id>" `
    -OwnerType "User" `
    -FormId "<form id>" `
    -PageSize 100 `
    -Skip 500 `
    -TokenManager $TokenManager

This way, you don’t need to re-fetch thousands of old responses — only the new ones that came in since your last run. Perfect for incremental automation jobs.

A Real-World Example: Getting Form Responses

To make this concrete, let’s walk through a simple example using the PowerShell functions we’ve covered.

  • Get the Form ID manually
    Open the form’s responses page in your browser. The URL will look like this:
Plaintext
https://forms.office.com/Pages/ResponsePage.aspx?id=_tLqPYCGw063v1PA4swe7lcR8JWHvpdGnz9zWKI-QV1UREFTSjMyUkM2WFlaUUJWQVpXOUgwMzlTRi4u

The long alphanumeric string after id= is the Form ID. Copy this value.

  • Get the User or Group ID from Entra ID (Azure AD)
    Use the user’s or group’s Object ID in Azure AD, which you’ll pass to the function to retrieve their forms.
  • Pull all responses with paging
PowerShell
<# Get All Responses with Paging Support #>
$responses = Get-MSFormResponses `
    -TenantId $TenantId `
    -OwnerId "<entra object id>" `
    -OwnerType "User" `
    -FormId "_tLqPYCGw063v1PA4swe7lcR8JWHvpdGnz9zWKI-QV1UREFTSjMyUkM2WFlaUUJWQVpXOUgwMzlTRi4u" `
    -PageSize 100 `
    -TokenManager $TokenManager

($responses.answers | ConvertFrom-Json)

This simple example demonstrates how easy it is to retrieve form responses programmatically. On the left, you see the standard Microsoft Forms responses page; on the right, the PowerShell output mirrors the same data in a structured format. While this example uses a single form and a single user, it proves the concept: with the token manager and paging support, you can scale this to large forms, multiple users, or even automated nightly data pulls.

Real-World Automation Scenarios

Once you can reliably fetch Forms data via PowerShell, you’re no longer locked into viewing results only in the web UI or exporting them manually. A few examples of how you might use this:

  • Exporting to CSV for Reporting
    Run the script nightly and dump responses into a CSV file that can be shared, archived, or imported into other systems. Perfect for lightweight reporting without needing Power BI licenses.
  • Saving to a Database
    Pipe responses directly into SQL Server, Azure SQL, or even a simple SQLite database. This gives you historical tracking, joins with other datasets, and more advanced analytics.
  • Integrating with a Ticketing System
    Imagine using Forms as an intake system for support or change requests. With automation, each new response could automatically create a Jira, ServiceNow, or Zendesk ticket — no manual copying and pasting required.
  • Triggering Workflows
    Hook the script into a CI/CD pipeline or an Azure Automation runbook so that responses can trigger downstream actions (e.g., provisioning resources, adding users, kicking off approvals).

In short, once Forms data becomes API-accessible, it can plug into almost anything.

Final Thoughts

While Microsoft hasn’t yet provided an official Graph API for Forms, this approach fills that gap by giving you a reliable way to query forms, fetch responses, and automate reporting with nothing more than PowerShell and an app registration. By leveraging application permissions, you avoid license dependencies from tools like Power BI or PowerApps, and instead gain a lightweight, cost-effective, and fully scriptable solution. Whether you’re building scheduled reports, archiving responses, or integrating survey data into larger workflows, this method provides the missing automation link for Microsoft Forms.

Stay Tuned for Part 2: In the upcoming post, we’ll delve into creating, deleting, and updating Microsoft Forms programmatically using PowerShell. Don’t miss out on these advanced automation techniques!

9 responses to “Unlocking the Hidden Microsoft Forms API with PowerShell & Azure App Registration”

  1. Ranjith Kumar avatar
    Ranjith Kumar

    Amazing post. Did you get a chance to work on the create forms use cases ? I have a requirement to create a Microsoft form and only allow a specific set of users to be able to access it.
    While the form creation can be done through the UI itself as it has only very few basic details, the users that I need to allow to access it is a huge set. This makes it very difficult to add users one by one manually. I tried adding a comma separated list of users in the relevant field but it doesn’t like it. I had to add one user after the other and let the web page resolve the name I entered. As this entire process would take a lot of time, I was looking for a way to use some kind of API to interact with the form so that I can just add a list of users to the share the form. Please let me know if you have made any progress in this regard.
    Again, Thank you for posting this blog. You did an amazing job documenting the steps.

    1. Thanks for the kind words Ranjith, I’m glad you found the post helpful. As you can see I’m new to blogging so it’s great to get some positive feedback. I’ve made a start on the follow-up post and have been able to create forms using the API with application permissions. I had to extend the TokenManager class to support tokens from both the v1 and v2 endpoints to get this working. I’m aiming to have the new post live by the end of the month. In the meantime, I’ll do some research into your use case around bulk user access and if it’s possible I can share a solution directly to the email you used to post this comment if you are happy with that?

    2. Hi Ranjith, just a quick follow-up. I’ve looked into your use case around bulk user access and have managed to add users to a form via the API. I’ve written a PowerShell function to do this and have replied to your email with screenshots of my proof of concept along with the code for you to try out. Let me know how you get on.

  2. Franky Van Caelenberg avatar
    Franky Van Caelenberg

    Hey Jack,
    I’ve reused the script that you posted. I only want to do reporting (at this moment) of where Forms are used.
    – User level: works
    – Group level: FAILS

    I’m using Application permissions (Microsoft Forms): Forms.ReadWrite.All, because in the future I also want to switch owners by script.
    Whenever i use the script to retrieve Forms information from a group, it fails and returns a 401 error. But when i copy paste the same endpoint URL in the browser, it works.

    Do you have any idea how to fix this?

    Kind regards,
    Franky

    1. Hey Franky,

      Thanks for commenting, great to hear the user-level reporting is working!

      I’ve run into a few caveats myself while digging deeper into the Forms API. It seems that different token endpoints are sometimes needed, for example when creating forms via the API, which might explain the 401s you’re seeing with group-owned forms.

      I’ve put together an updated TokenManager class that you could test with to see if it helps. I’ll email that across shortly, and I’ll also be including it in my follow-up post (Part 2). I can take a look in my own environment later this week as well and report back with what I find.

      Appreciate you flagging this.

      Thanks,
      Jack

  3. Hi Jack, Thanks for this amazing post, same as mentioned above, the user forms are working but groups are failing for me. Can you post the token manager class here or email me?

    Regards,
    Van

    1. Hi Van,

      I’m really glad you found the post useful! Unfortunately, it looks like the Groups endpoint isn’t supported with application permissions. From some testing I did yesterday, I found that you can access the Groups endpoint successfully using an ROPC flow with an account that’s excluded from MFA (using delegated permissions rather than application permissions).

      For this to work, the app registration needs to include the Forms.Read delegated permission with admin consent granted. However, there’s still a caveat: you’ll only be able to see forms owned by groups that the authenticated account is a member of.

      I’ll be updating the post over the next week so others don’t run into the same issue. I’ll also email you the updated TokenManager class shortly.

      Thanks,
      Jack

  4. Hi Jack, Thanks for the token manager sent to my mail, I was able to pull the group forms using ROPC as well now. If you can post the entire script in one single text, then please do. That will be super helpful. Thanks again..!!

    1. Hi Van,

      I’m glad the updated TokenManager ROPC solution worked for you. That’s a great idea to have the full code in one place. I’m currently working on part 2, so I’ll make sure to do this going forward and will provide the full solution for this post as well, most likely on my GitHub.

      Thanks for the feedback!

Leave a Reply

Your email address will not be published. Required fields are marked *