The Microsoft Graph PowerShell SDK is a great and simpler ways to get MS Graph API PowerShell code working quickly. But what I have found the source code and example to utilize the X509 certificate ways of authentication. For doing a quick demo with the Azure AD security token there a simple way which I will describe here in this post.
Script example
The tip is very simple. Since Connect-MgGraph does not have Client Secret parameter, use the Invoke-RestMethod to get the access token. Once valid token is received pass it to the Connect-MgGraph and make the rest of the other MS Graph SDK calls after that.
See in the following example I have used the Get-MgGroup call after successfully connecting to MS Graph.
# The following command only required one time execution
if ( Get-ExecutionPolicy)
{
Write-Host "RemoteSigned policy exists."
}
else
{
Write-Host "RemoteSigned policy does not exist."
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
}
if (Get-Module -ListAvailable -Name Microsoft.Graph) {
Write-Host "Microsoft.Graph Module exists"
}
else {
Write-Host "Microsoft.Graph Module does not exist"
Install-Module Microsoft.Graph -Scope AllUsers
}
# Populate with the App Registration details and Tenant ID
$ClientId = "TODO"
$ClientSecret = "TODO"
$tenantid = "TODO"
$GraphScopes = "https://graph.microsoft.com/.default"
$headers = @{
"Content-Type" = "application/x-www-form-urlencoded"
}
$body = "grant_type=client_credentials&client_id=$ClientId&client_secret=$ClientSecret&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default"
$authUri = "https://login.microsoftonline.com/$tenantid/oauth2/v2.0/token"
$response = Invoke-RestMethod $authUri -Method 'POST' -Headers $headers -Body $body
$response | ConvertTo-Json
$token = $response.access_token
# Authenticate to the Microsoft Graph
Connect-MgGraph -AccessToken $token
# If you want to see debugging output of the command just add "-Debug" to the call.
Get-MgGroup -Top 10
Conclusion
I hope this helps you. I use this technique to quickly check / test the calls to the MS Graph.
Note: Please make sure your Azure AD app has required permission applied and consented or else you would get “Insufficient privileges to complete the operation.” error.
Also use the MS Graph explorer as UI ways to test your API and check required permission.
The PnP PowerShell command Get-PnPTenantSite to get all sites from the tenant takes longer time. Additionally, it does not have asynchronous ways to get the information in the Azure Durable Function.
This article uses the MS Graph API List Sites to get the sites. To use this API the following Application API permissions required for the Azure AD app.
Sites.Read.All, Sites.ReadWrite.All
Script
$StartMs = (Get-Date).Millisecond
# You will need Azure AD app with the following API permissions.
# Application Sites.Read.All
#
$ClientId = "TODO"
$ClientSecret = "TODO"
$tenantid = "TODO"
$path2File = 'C:\temp\test.txt' # Change this as you like.
## Get Auth Token ##
$headersAuth = @{
"Content-Type" = "application/x-www-form-urlencoded"
'Accept' = '*/*'
}
$body = $("grant_type=client_credentials&client_id={0}&client_secret={1}&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default" -f $ClientId, $ClientSecret)
$outhTokenUrl = $("https://login.microsoftonline.com/{0}/oauth2/v2.0/token" -f $tenantid)
$response = Invoke-RestMethod $outhTokenUrl -Method 'POST' -Headers $headersAuth -Body $body
$response | ConvertTo-Json
$tokenExpiryTime = (get-date).AddSeconds($response.expires_in)
##
## Make the first call with $filer to your tenant name ##
##
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Content-Type", "application/json")
$headers.Add("SdkVersion", "postman-graph/v1.0")
$headers.Add("Prefer", "apiversion=2.1")
$headers.Add("Authorization", $("Bearer {0}" -f $response.access_token) )
$response = Invoke-RestMethod 'https://graph.microsoft.com/v1.0/sites?$filter=siteCollection/hostname eq ''{CHANGE TO YOUR TENANT NAME}.sharepoint.com''' -Method 'GET' -Headers $headers
$response | ConvertTo-Json
## Check if there are more sites to fetch...
while ( $response.'@odata.nextLink' -ne $null )
{
## iterate on the response and write the site url to the file.
foreach ( $val in $response.Value )
{
Write-Output $("{0}" -f $val.webUrl)
Add-Content -Path $path2File -Value $val.webUrl
}
# check if the token expired, if it did get a new one.
if ( (get-date) -gt $tokenExpiryTime )
{
$response = Invoke-RestMethod "https://login.microsoftonline.com/$($tenantid)/oauth2/v2.0/token" -Method 'POST' -Headers $headers -Body $body
$response | ConvertTo-Json
$tokenExpiryTime = (get-date).AddSeconds($response.expires_in)
# modify header with the new token
$headers = @{
"Content-Type" = "application/json"
"Authorization" = $("Bearer {0}" -f $response.access_token)
}
}
# first store the data of Web URL to the file.
$response = Invoke-RestMethod $response.'@odata.nextLink' -Method 'GET' -Headers $headers
$response | ConvertTo-Json
}
$EndMs = (Get-Date).Millisecond
WriteHost "This script took $($EndMs - $StartMs) milliseconds to run."
Conclusion
Using MS Graph API you can overcome to get the list of all sites within your tenant. This can be done using the Get-PnPTenantSite but it has overheads if you just want the site urls of all sites.
The first row with the hideWelcome as Yes will hide the welcome message.
The hidden welcome message
Conclusion
The above trick may not work for all scenarios. As I have not tested all scenario. This is a technique to hide the welcome message based on the hidden field named hideWelcome.
Step # 6: Now make the Get Profile Image call to get the Image content of the profile photo.
Make an HTTP call to get the profile image.
# Please use the highlighted values for URI and Headers.
{
"inputs": {
"method": "GET",
"uri": "https://graph.microsoft.com/v1.0/users/@{triggerBody()['text']}/photo/$value",
"headers": {
"responseType": "blob",
"Content-Type": "blob",
"Authorization": "@{body('ParseJSONforToken')?['token_type']} @{body('ParseJSONforToken')?['access_token']}"
}
},
"metadata": {
"operationMetadataId": "775579d0-6aa9-4326-a1f9-0ad37217c304"
}
}
Step # 7: Finally add compose section to get the image content from the above action.
Conclusion
As shown in the above technique you can get the user profile images on the tenant. The Azure AD app plays an important part to make an MS Graph API call. The same API call can be made using the PUT and you should be able to apply a badge or anything else to the user profile picture.
Step # 1 Create an Azure AD app with the MS Graph “DeviceManagementManagedDevices.Read.All” permission.
MS Graph “DeviceManagementManagedDevices.Read.All” permission.
Please note the Application ID, Secret, and Tenant ID. You will need these three pieces of information in the PowerShell Script.
Step # 2 Using PowerShell run the following script.
# Init Variables
$outputPath = "C:\Hold"
$outputCSVPath = "C:\Hold\EAWFAreport.zip" #might need changed
$ApplicationID = "TOBE CHANGED"
$TenantID = "TOBE CHANGED"
$AccessSecret = "TOBE CHANGED"
#Create an hash table with the required value to connect to Microsoft graph
$Body = @{
grant_type = "client_credentials"
scope = "https://graph.microsoft.com/.default"
client_id = $ApplicationID
client_secret = $AccessSecret
}
#Connect to Microsoft Graph REST web service
$ConnectGraph = Invoke-RestMethod -Uri https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token -Method POST -Body $Body
#Endpoint Analytics Graph API
$GraphGroupUrl = "https://graph.microsoft.com/beta/deviceManagement/reports/exportJobs"
# define request body as PS Object
$requestBody = @{
reportName = "Devices"
select = @(
"DeviceId"
"DeviceName"
"SerialNumber"
"ManagedBy"
"Manufacturer"
"Model"
"GraphDeviceIsManaged"
)
}
# Convert to PS Object to JSON object
$requestJSONBody = ConvertTo-Json $requestBody
#define header, use the token from the above rest call to AAD.
# in post method define the body is of type JSON using content-type property.
$headers = @{
'Authorization' = $(“{0} {1}” -f $ConnectGraph.token_type,$ConnectGraph.access_token)
'Accept' = 'application/json;'
'Content-Type' = "application/json"
}
#This API call will start a process in the background to #download the file.
$webResponse = Invoke-RestMethod $GraphGroupUrl -Method 'POST' -Headers $headers -Body $requestJSONBody -verbose
#If the call is a success, proceed to get the CSV file.
if ( -not ( $null -eq $webResponse ) )
{
#Check status of export (GET) until status = complete
do
{
#format the URL to make a next call to get the file location.
$url2GetCSV = $("https://graph.microsoft.com/beta/deviceManagement/reports/exportJobs('{0}')" -f $webResponse.id)
"Calling $url2GetCSV"
$responseforCSV = Invoke-RestMethod $url2GetCSV -Method 'GET' -Headers $headers -verbose
if (( -not ( $null -eq $responseforCSV ) ) -and ( $responseforCSV.status -eq "completed"))
{
#download CSV from "URL=" to OutputCSVPath
#### It means the completed status is true, now get the file.
Invoke-WebRequest -Uri $responseforCSV.url -OutFile $outputCSVPath
# Un Zip the file.
Expand-Archive -LiteralPath $outputCSVPath -DestinationPath $outputPath
}
{
Write-Host "Still in progress..."
}
Start-Sleep -Seconds 10 # Delay for 10 seconds.
} While (( -not ( $null -eq $responseforCSV ) ) -and ( $responseforCSV.status -eq "inprogress"))
}
After this PowerShell script call, you should see the Zip and CSV files in the C:\Hold Folder.
Conclusion
The above steps will help you to get the InTune reports data file. The API still in the beta if anything changes I will update this post.
My customer has migrated the classic sites to SharePoint Online. Some sites’ home pages are with the list view web part, these pages make multiple query calls to Search API every 60 seconds.
This will make Search API throttle the query and it may have a bad user experience if many users frequently visit the same page multiple times. Read this article on the details of throttling.
Step by Step to diagnose the issue
For an issue like throttling, to diagnose you will need the Browser’s Developer Tools, you can get more information here about the developer tool for the Edge browser.
Go to the SharePoint page with List View WebPart. Open the Network tab and watch for the outbound traffic. You will notice every 60 seconds the ListView WebPart will try to refresh.
This happens because the following are the settings for the list view web part.
“Automatic Refreshing interval (seconds)”
Increase the auto-refresh interval and turn it on to show the manual refresh button.
Conclusion
This may be a simple thing and can cause issues only if the page is popular and many users are visiting at the same time. If the user leaves the page active for a long time the auto-refresh will make the call in the interval and the user may not even need the refresh.
The better fix for such a page, increase the interval and provide the manual refresh button so as per the user’s need they can refresh. This will also reduce unnecessary calls to the Search API.
The following command can be used to set the Versionins and Major / Minor version numbers.
# In this command the minor version in the UI (as aboove) is the draft version.
Set-PnPList -Identity "Documents" -EnableVersioning $true -MajorVersions 25 -EnableMinorVersions $true -MinorVersions 10
#
There are two files to automate the process of turning on versioning. The input file is site2process.CSV. The input file is with list of Site Collection URLs to process.
The following are the customer concerns with the SharePoint sites on Microsoft 365 cloud regarding audit reports.
SPO site collection admins do not receive the same GUI presentation for site audit reports that were available on the SharePoint on-premises.
Currently, the reports are available only in the CSV files.
Currently, O365Tenant admins must run the report to get the CSV files
As per the customer, in the SharePoint on-premises SCA could set the following audit options and pull it from the Site Settings. The same is not possible in SPO.
Opening or downloading documents, viewing items in lists, viewing item properties
Editing items, Checking out or checking in items, Moving or copying items to another location on the site, Deleting or restoring items
Editing content types or columns, Searching site content, Editing users and permissions
SharePoint on-premises these options events were available to audit.
Step by Step Solution
The SharePoint online workload in Microsoft 365 tracks audit logs data at the tenant level. The tenant administrator can get these data (in CSV format) from the compliance center portal, which is a manual process for the admin.
To automate the process Office 365 Management API can be used to get these audit data. There are two types of Office 365 Management API?
Office 365 Service Communications API, which can do the following:
Get Services: Get the list of subscribed services.
Get Current Status: Get a real-time view of current and ongoing service incidents.
Get Historical Status: Get a historical view of service incidents.
Get Messages: Find Incident and Message Center communications.
Office 365 Management Activity API, which can do the following.
Use the Office 365 Management Activity API to retrieve information about the user, admin, system, and policy actions and events from Office 365 and Azure AD activity logs.
Audit and activity logs to create solutions that provide monitoring, analysis, and data visualization.
The Office 365 Management Activity API allows for pulling of audit logs for the following workloads.
Audit.AzureActiveDirectory
Audit.Exchange
Audit.SharePoint
Audit.General (includes all other workloads not included in the previous content types)
DLP.All (DLP events only for all workloads)
Step # 1 In Compliance Center turn on auditing
Go to https://compliance.microsoft.com and sign in.
In the left navigation pane of the Microsoft 365 compliance center, click Audit.
If auditing is not turned on for your organization, a banner is displayed prompting you to start recording user and admin activity.
Click the Start recording user and admin activity banner.
It may take up to 60 minutes for the change to take effect.
Compliance Center to turn on auditing
Step # 2 Register an Azure AD App
To register the Azure AD application you can follow this step.
Add and grant consent to the following Application permissions. – Office 365 Management API 1. ActivityFeed.Read 2. ActivityFeed.ReadDlp 3. ServiceHealth.Read
Step # 3 Start a subscription for a workload(s)
Prior to making this call you will need to get the Access Token by using the Azure AD app. The following call will add Audit.SharePoint, you can add more workloads.
*** REQUEST TO BE MADE ***
POST {root}/subscriptions/start?contentType=Audit.SharePoint&PublisherIdentifier={TenantGUID}
Content-Type: application/json; utf-8
Authorization: Bearer eyJ0e...Qa6wg
RESPONSE
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
"contentType": "Audit.SharePoint",
"status": "enabled",
}
Step # 4 Activity API operations
After one time subscription, you can make a call to the feed API to get audit information. The subscription can be stopped as well.
*** MAKE a Call to get the list of Audit reports ***
*** StartTime and End Time must not exceed 24 hours ***
*** The times must be in the form of yyyy-MM-ddTHH:mm:ss.fffZ ***
*** PowerShell $startTimeDt.ToString("yyyy-MM-ddTHH:mm:ss.fffZ")" ***
REQUEST
GET https://manage.office.com/api/v1.0/{{TenantID}}/activity/feed/subscriptions/content?
startTime={{startTime}}&
endTime={{endTime}}&
contentType={{contentType}}&
PublisherIdentifier={{TenantID}}
RESPONSE
[
{
"contentUri": "https://manage.office.com/api/v1.0/b07282ed-2513-42ff-8322-de55ebce98f1/activity/feed/audit/20220404172343761148151$20220405052516832093374$audit_sharepoint$Audit_SharePoint$na0034",
"contentId": "20220404172343761148151$20220405052516832093374$audit_sharepoint$Audit_SharePoint$na0034",
"contentType": "Audit.SharePoint",
"contentCreated": "2022-04-05T05:25:16.832Z",
"contentExpiration": "2022-04-11T17:23:43.761Z"
},
{
.... removed for brevity
]
Step # 5 Using the above response for each “contentUri” make another get call to get the JSON response
As you can see from the response there are multiple data in the JSON response. These data can be different based on what content type is subscribed. Here is the schema for all response values.
After the above steps you should be able to see the audit data. But these are all manual steps to get the content. To automate these you will need to use the Azure resources such as Azure Storage, Azure Functions, and Azure Cosmos DB. The following is the logical architecture that depicts those Azure resources.
Logical Architecture for automating to get the audit data
The Power BI Premium license is required for the dashboard.
List of required Azure resources for the automating the audit log solution
To create the above resources in your Azure demo tenant you can use this ARM template.
The Queue Events Azure Function Code is located here.
The Store Events Azure Function Code is located here.
Once the data is gathered in the Azure Cosmos DB, the Power BI can generate a report similar to the following. The report shown is a basic example but once the data is collected to the Azure Cosmos DB you can create more such reports to meet your audit need.
The report can answer the following questions:
Who did an operation to the File, List, or Library?
When the operation was performed?
What operation was performed?
What sites are used?
The Audit report from the audit data gathered in the Azure Cosmos DB.
The above solution is a proof of concept (POC) for SharePoint or OneDrive data. There are other workloads audit data that can collect such as Exchange, DLP, Azure AD, etc.
This is the final part of the three-part blog post.
Part1 and Part2 posts were focused on the basic settings for the Classic UI mode pipeline setup. In this post, I will explain more advanced, modular, and factory pattern ways of the CI/CD Azure DevOps pipelines.
My customer has Azure DevOps Server (ADOS) and pool agent as Windows OS for the default pool.
As defined in Part1, the prerequisite still applies to this approach. Additionally, you will need to have some basic familiarity with the YAML language. You will need the Git installed on your local machine. This approach uses the Microsoft Power Platform CLI. The Pac CLI performs better than Power Platform Build Tools defined in Part2.
Step by Step solution
Step # 1 Create an organization and project in your Azure DevOps.
Create a new project in the Azure DevOps organization
Copy the above two folders to the cloned local repo.
Step # 5 Using the following Git commands push the changed code to the Azure DevOps repo.
# this will check the status as untacked changes
git status
# this command will add the untracked changes to the local as tracked
git add .
# this command will commit to the local repository
git commit -m "Added .azdops and pwsh folders"
# this command will push the local commited changes to the remote repoository
git push
The four commands to push the two folders and files to the Azure DevOps repo.
The final results to the Azure DevOps repository should look similar to the following. The .azdops and pwsh folders should be the top folder in your repository.
The Azure DevOps repository final result.
Step # 6 Create Azure Pipelines variable groups ‘Credentials’, ‘Dev’, ‘Test’, and ‘Prod’.
In the ADO project, click on the Library and Add the four variable groups. Now create variables in these variable groups. These variable groups are used in the YAML code. Please keep the name as defined below for variable groups and variables.
Add four variable groups.
#
# Variable Group Crendential will have the following three variables.
#
ClientId = {Get the value from the Part1 blog post Step # 3 }
ClientSecret = {Get the value from the Part1 blog post Step # 3 }
TenantId = {Get the value from the Part1 blog post Step # 3 }
#
# Variable Group 'Dev', 'Test', 'Prod' will have the following variable.
# The Url value will be different for all three.
#
Url = {Get the value from the Part1 blog post Step # 2 }
Note: The above values will be used in the pipeline.
Under pipelines -> environments, define test and prod environments. The environments can be used for adding any approval requirements.
Create test and prod environments
For the prod environment, (optional) add the approval settings. Assign one or more approvers.
Assign approver to the prod environment.
Step # 9 Create a Pipeline using the existing file .azdops\pipelines\export-commit.yml
(Note: a longer step)
Click Create Pipeline button under the Pipelines
Select Azure Repos Git
Select the Repository for the project.
Select the ‘Existing Azure Pipelines YAML file.
Select ‘export-commit.yml’ file
Finally, review the code and click the arrow to save the pipeline. DO NOT RUN at this time.
Now rename the newly created pipeline by clicking 1, 2, and 3 as shown above.
Rename it to “Export and Commit by Solution Name Variable”
First, add the variable ‘SolutionName’ before running the pipeline.
Note: You will get the following error when you run the pipeline. YOU NEED TO set Contribute Allow for the build agent.
“remote: TF401027: You need the Git ‘GenericContribute’ permission to perform this action.”
An error when you run the pipeline the first time.
Change the Contribute settings to Allow for the Build Service agent.
Upon successful pipeline run, you should see your Solution under the solution folder as shown above.
Step # 8 Setup multi-stage build and deploy pipeline for .azdops\pipelines\build-deploy.yml
Consider the file .azdops\pipelines\build-deploy.yml as the template for your Power Platform Solution. From the above example for ‘Spark2022’ solution name I created the copy of the template to a new file named ‘Spark2022-build-deploy.yml’.
Make a copy of ‘build-deploy.yml’ template file for your solution. Replace the Demo2022 text from the yml file.
Step # 10 Create a Pipeline using the existing file .azdops\pipelines\{YOUR SOLUTION NAME}-build-deploy.yml
Note: You will repeat most of the solution steps from Step # 7 above. Please refer above for the screenshots.
Click on Pipeline, then click the “New pipeline” button.
Select Azure Repos Git
Select your specific repository
Select the “Existing Azure Pipelines YAML file”
Select the /.azdops/pipelines/{YOURSOLUTIONNAME-build-deploy.yml file. (You created in the step # 8)
DO NOT Run yet, click on the down arrow next to the Run blue button. Select Save.
Now click on Pipelines from the left menu. Click on the “All” tab.
Click on the newly created pipeline to rename, see below.
Rename the new pipeline.
Rename to something like “{YourSolutionName} Build and Deploy”. So for the above example, it would be “Spark2022 Build and Deploy”
Rename to “{YourSolutionName} Build and Deploy”
Step # 11 Create config.test.json and config.prod.json files (if not present) under the solution folder.
The two config files for the test and prod are under your Solution Folders directory.
Two config files for test and prod with empty JSON for now.
The save of these files will trigger the build if not, run the pipeline, you will see the multi-stage for Build, Test, and Prod
NOTE: In this pipeline, you do not need to set any variable before running it.
The build and deploy three stages.
Step # 12 Regarding the config.test.json and config.prod.json files.
This is an optional step. You may have environment variables for the connection references, site URL, list guide, etc. These variables are different in each environment. The values for deployment can be configured in the config.ENV.json file.
#
# Use the *unmanged* solution to extract dev config setting.
#
pac solution create-settings --solution-zip Spark2022.zip --settings-file config.dev.json
Dev config Settings using Pac CLI.
CITIZEN DEVELOPER SCENARIO
All above steps 1 to 12 are for the Build Administrator or Technical lead of the project.
For the Citizen developer’s role, the steps are simple and as follows.
Gather the requirements or feedback or bugs for the application.
Create a Power Platform Solution in the Dev Environment e.g. LaptopInventory
Develop a PowerApps App
Develop a PowerAutomate Flow
Creates a List and any other components required for the solution.
Test the application and flow
Run Export and Commit Pipeline (export-commit.yml) for the LaptopInventory solution.
Note: you need to add a SolutionName=LaptopInventory variable before running the pipeline
This action will create a new directory ‘LaptopInventory’ in the repository
Create config.test.json and config.prod.json files with an empty record as {}
Note: This is a one-time activity. It can be repetitive if you are adding any config variables for various environments.
Create a Build and Deploy Pipeline for the solution ‘LaptopInventory’ (See Step#8 and Step#9)
Note: This is a one-time activity
Make a copy of build-deploy.yml to LaptopInventory.yml in the ‘.azdops\piplelines‘ folder
Change the text from ‘Demo2022’ to the ‘LaptopInventory’
Create a Pipeline using the new YML file
Run the pipeline if it is not running already. (This action will make the Solution available in the Test Environments)
Ask Testers to test and provide feedback.
Repeat the above steps from here as needed for the ALM cycle.
The below flow chart gives an overview of the process. The developers run the “Export & Commit” by the SolutionName. This action “1” exports the Solution and Commits to the Source Control Managemen (SCM). The checking in the file(s) for the solution auto triggers the “Build & Release” pipeline which eventually put the managed solution in the TEST or PROD environments.
Depending on the feedback from the testers the above process is repeated by the developer.