Updating users' Microsoft profile pictures with PowerShell - powershell

Our company is migrating user info to our Microsoft tenant. Part of this info includes the profile picture.
Most of the information I am able to update using Set-MSolUser, but for the profile picture I've been trying to use Microsoft Graph, with no success.
I am a tenant admin and yet I've had no success in updating users' profile pictures. Here's my code:
$token = [my-token]
$Headers = #{
"Authorization" = "Bearer $token"
"Content-Type" = "image/jpeg"
}
Invoke-RestMethod -Uri 'https://graph.microsoft.com/v1.0/users/{user-id}/photo/$value' -Method Put -Headers $Headers
This has just been for test purposes so I'm trying with a single user id and no picture. This has been the output:
"error": {
"code": "ErrorAccessDenied",
"message": "AccessDeniedException",
And the same thing happens when querying directly through the graph website:
Per the documentation, certain permissions are necessary (contact, group or user read.write) which I have ticked all on the Graph website, but still nothing.
Any ideas would be greatly appreciated.

Under user permissions you able to edit only your own picture.
You have to create an Azure AD application with User.ReadWrite.*All* permission to edit others' pictures.
Go to:
portal.azure.com -> Active Directory -> App Registration -> New Registration. Then under API permissions you grant User.ReadWrite.All and click grant admin consent. Then under Certificates and Secrets you create an app secret.
$AzAppSecret = 'abcDEFghiJKLmnoPQRstuVWXyz01234567' # from AzureAD -> App Registrations -> YourApp -> Certificates & secrets
$AzAppId = 'AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA' # from AzureAD -> App Registrations -> YourApp -> Overview -> Application (client) ID
$AzTenantId = 'BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB' # from AzureAD -> App Registrations -> YourApp -> Overview -> Directory (tenant) ID
$AzUserUPN = 'username#example.com' # from AzureAD -> Users -> YourUser -> User Principal Name
$AzUserImage = 'S:\samplepic.jpg' # Jpeg file
# Request token
$tokenRequestBody = #{
Grant_Type = "client_credentials"
Scope = "https://graph.microsoft.com/.default"
Client_Id = $AzAppID
Client_Secret = $AzAppSecret
}
$tokenRequestUri = [String]::Format('https://login.microsoftonline.com/{0}/oauth2/v2.0/token', $AzTenantId)
$tokenResponse = Invoke-RestMethod -Uri $tokenRequestUri -Method 'POST' -Body $tokenRequestBody -ErrorAction Stop
$accessToken = $tokenResponse.access_token
$uri = [String]::Format('https://graph.microsoft.com/v1.0/users/{0}/photo/$value', $AzUserUPN)
$Headers = #{
'Authorization' = [String]::Format('Bearer {0}', $accessToken)
'Content-Type' = 'image/jpeg'
}
Invoke-RestMethod -Method Put -Uri $uri -InFile $AzUserImage -Headers $Headers
Alternative way:
# Load Assembly System.Net.Http
# In PS7 built-in, on PS5 - Download NuGet package from https://www.nuget.org/packages/System.Net.Http/ and unzip using 7zip
[void][System.Reflection.Assembly]::LoadFile('P:\path-to\system.net.http.4.3.4\runtimes\win\lib\netstandard1.3\System.Net.Http.dll')
# Prepare httpClient and URI
$httpClient = [System.Net.Http.HttpClient]::new()
$httpClient.DefaultRequestHeaders.Authorization = [String]::Format('Bearer {0}', $accessToken)
# Prepare Content
$content = [System.Net.Http.ByteArrayContent]::new([System.IO.File]::ReadAllBytes($AzUserImage));
$content.Headers.ContentType = "image/jpeg";
# Run
$task = $httpClient.PutAsync($uri,$content)
$task.Wait()
$task.Result.IsSuccessStatusCode
Remember to keep Application secret private

This is expected error in case if
for app using application permissions, User.ReadWrite.All permission is missing when calling this Microsoft Graph endpoint
token is acquired with delegated permissions
Documentation says the following in this regard:
update the photo of any user in the organization, your app must have
the User.ReadWrite.All application permission and call this API
under its own identity, not on behalf of a user
And last but not least, profile image is expected to be passed via request body, in case of PowerShell the request could be constructed like this:
$Headers = #{
"Authorization" = "Bearer $access_token"
"Content-Type" = "image/jpeg"
}
$url = "https://graph.microsoft.com/v1.0/users/$($userId)/photo/$value"
$profilePath = "--path to profile file--"
Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users/$($userId)/photo/$value" -Method Put -InFile $profilePath -Headers $Headers

Related

how to access sharepoint folder using graph api

In my SharePoint site Document Library, I have a folder called OneDriveInventory and I have a few file inside.
I'm trying to access those file but it look like my Uri request is wrong so I would be really appreciated I can get any help or suggestion on how to fix this.
Invoke-WebRequest -Uri "https://graph.microsoft.com/v1.0/sites/$siteId/drives/$driveId/OneDriveInventory/$($filename)" -Method GET -OutFile $filePath -Headers $headers
I tried to reproduce the same in my environment and got below results:
I have one SharePoint document library in which I created folder named OneDriveInventory and added files like below:
You can make use of below Graph API call to access file in the above folder:
GET https://graph.microsoft.com/v1.0/sites/<siteID>/drives/<driveID>/root:/OneDriveInventory:/children/<filename>
When I ran the above query in Graph Explorer, I got results successfully like below:
To get the same results from PowerShell, I registered one Azure AD application and added API permissions like below:
Now I generated access token using below PowerShell script:
$tenantId = <tenantID>
$applicationId = <appID>
$secret= <secret>
$param = #{
Uri = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token";
Method = 'Post';
Body = #{
grant_type = 'client_credentials';
scope = 'https://graph.microsoft.com/.default';
client_id = $applicationId;
client_secret = $secret
}
}
$result = Invoke-RestMethod #param
$token = $result.access_token
Response:
To access file from folder, I used below PowerShell script:
$siteID = "3b23b598-f5e4-461f-b038-xxxxxx"
$driveID = "xxxxxxxxxxxxxxxxxxx"
$filename = "sridevi.txt"
$headers=#{}
$headers.Add("Content-Type", "text/plain")
$headers.Add("Authorization", "Bearer $token")
$response=Invoke-WebRequest -Uri "https://graph.microsoft.com/v1.0/sites/$siteID/drives/$driveID/root:/OneDriveInventory:/children/$($filename)" -Method GET -OutFile $filePath -Headers $headers
Response:
When I ran $response.Content, I got full response like below:

How to change a user password using Microsoft Graph API

I'm writing a kind of passwd command line app to change an Azure account's password. Something to work like this:
> passwd someuser Passw*rd1
After reading all the documentations, I have my application created in Azure portal with all the permissions needed and I'm about to get the app authorization, just before getting the access token.
Problem is instead of getting the authorization code what the call for https://login.microsoftonline.com/<tenantName>/oauth2/v2.0/authorize returns to me is an HTML page.
As far as I understand, this page is supposed to give the signed-in user the chance to delegate the required permissions, but this is a command to be used by an administrator user. In fact, there is no signed-in user at this moment.
What am I missing?
There is a code sample about resetting user password.
$tennantid = ''
$SubscriptionId = ''
$ApplicationID = ''
$ApplicationKey = ''
$TokenEndpoint = {https://login.windows.net/{0}/oauth2/token} -f $tennantid
$ARMResource = "https://graph.microsoft.com";
$Body = #{
'resource'= $ARMResource
'client_id' = $ApplicationID
'grant_type' = 'client_credentials'
'client_secret' = $ApplicationKey
'scope' = 'https%3A%2F%2Fgraph.microsoft.com%2FDirectory.AccessAsUser.All'
}
$params = #{
ContentType = 'application/x-www-form-urlencoded'
Headers = #{'accept'='application/json'}
Body = $Body
Method = 'Post'
URI = $TokenEndpoint
}
$token = Invoke-RestMethod #params
$headers = #{}
$headers.Add("authorization","Bearer $($Token.access_token)")
$ResetPwd = #{
"passwordProfile" = #{
"forceChangePasswordNextSignIn" = "false"
"password" = "Test123456!"
}
} | ConvertTo-Json
Invoke-RestMethod -Headers $headers -Method Patch -Uri "https://graph.microsoft.com/beta/users/$($respons.id)" -ContentType "application/json" -Body $ResetPwd
Then used this and the code above works.
$servicePrincipal = Get-MsolServicePrincipal -ServicePrincipalName ServicePrincipalName
$roleId = (Get-MsolRole -RoleName "Company Administrator").ObjectId
Add-MsolRoleMember -RoleObjectId $roleId -RoleMemberObjectId $servicePrincipal.ObjectId -RoleMemberType servicePrincipal
Hope this help.
Where are you call the /authorize endpoint?
You could call it like this in a browser and log in with your account:
https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize?
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&response_type=code
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&response_mode=query
&scope=openid%20offline_access%20https%3A%2F%2Fgraph.microsoft.com%2Fuser.read
&state=12345
Then you will get a "code" in the address bar.
Use this code to request access token:
POST /{tenant}/oauth2/v2.0/token HTTP/1.1
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&scope=https%3A%2F%2Fgraph.microsoft.com%2Fuser.read
&code=OAAABAAAAiL9Kn2Z27UubvWFPbm0gLWQJVzCTE9UkP3pSx1aXxUjq3n8b2JRLk4OxVXr...
&redirect_uri=http%3A%2F%2Flocalhost%2Fmyapp%2F
&grant_type=authorization_code
&client_secret=JqQX2PNo9bpM0uEihUPzyrh
See details from Request an authorization code and Request an access token.

Microsoft Graph API - app in powershell - forbidden response

I am attempting to make a Microsoft Graph API call using powershell 5.1. I have Registred the app. I use the AppID and secret to obtain an Authorization Bearer token. I then construct my API request. The security/events endpoint only returns a Forbidden response.
API Permissions Granted
My script to make the call looks like this:
# Define AppId, secret and scope, your tenant name and endpoint URL
$AppId = [myAPPID]
$AppSecret = [myAPPSecret]
$Scope = "https://graph.microsoft.com/.default"
$TenantName = "[mytenant].onmicrosoft.com"
$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"
# Add System.Web for urlencode
Add-Type -AssemblyName System.Web
$Body = #{
client_id = $AppId
client_secret = $AppSecret
scope = $Scope
grant_type = 'client_credentials'
}
# Splat the parameters for Invoke-Restmethod for cleaner code
$PostSplat = #{
ContentType = 'application/x-www-form-urlencoded'
Method = 'POST'
# Create string by joining bodylist with '&'
Body = $Body
Uri = $Url
}
$Request = Invoke-RestMethod #PostSplat
$Header = #{
Authorization = "$($Request.token_type) $($Request.access_token)"
}
$Uri = "https://graph.microsoft.com/v1.0/security/events"
$SecurityAlertsRequest = Invoke-RestMethod -Uri $Uri -Headers $Header -Method Get -ContentType "application/json"
Just looking to see If I have missed something obvious. Is there any other reason this would response with Forbidden for this configuration?
You have granted delegated permissions to the app.
Those only apply when there is a user signed in to your app and you acquire an access token to act on their behalf.
You need to grant application permissions to your app in Azure AD.

How to authenticate to Azure REST API using username and password (no App Id)

I need to acquire access token for accessing resources in Azure (https://management.azure.com endpoint) using REST API. Every article a have read, was counting with Appliction Id. In my case, the Azure tenant was just created (programatically) and I have to create some resources in it.
Only thing I have is tenant id, subscription id, user name and password of admin account. How can I authenticate using only information I have? How it works in PowerShell, that does not need to use an Application Id?
Based on my knowledge, it is impossible. As junnas said, even you use user/password authentication, client id is also required.
It is easy for you to create a service principal on Azure, you could check this link.
After the sp is created, you will get the client id, client secret. You also need give the sp Owner role on subscription, you could check this link.
Now, you could use the sp to call rest api in Power Shell, for example.
##get token
$TENANTID=""
$APPID=""
$PASSWORD=""
$result=Invoke-RestMethod -Uri https://login.microsoftonline.com/$TENANTID/oauth2/token?api-version=1.0 -Method Post -Body #{"grant_type" = "client_credentials"; "resource" = "https://management.core.windows.net/"; "client_id" = "$APPID"; "client_secret" = "$PASSWORD" }
$token=$result.access_token
##set subscriptionId and resource group name
$subscriptionId=""
$resourcegroupname="shui5"
$Headers=#{
'authorization'="Bearer $token"
'host'="management.azure.com"
'contentype'='application/json'
}
$body='{
"location": "northeurope",
"tags": {
"tagname1": "test-tag"
}
}'
Invoke-RestMethod -Uri "https://management.azure.com/subscriptions/$subscriptionId/resourcegroups/${resourcegroupname}?api-version=2015-01-01" -Headers $Headers -Method PUT -Body $body
You can use the Microsoft PowerShell Application ID to authenticate without having your own Application ID. This code snipet will give you the token:
Import-Module MSOnline # IMPORTANT! Loads type assembly Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext
$TENANTID = "" # Your Tenant ID
$clientId = "1b730954-1685-4b74-9bfd-dac224a7b894" # PowerShell Client Id
$MSMgmtURI = "https://management.core.windows.net"
$authority = "https://login.microsoftonline.com/$TENANTID"
$redirectUri = "urn:ietf:wg:oauth:2.0:oob"
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
$authResult = $authContext.AcquireToken($MSMgmtURI, $clientId, $redirectUri, "Always")
$token = $authResult.AccessToken
$headers = #{'Authorization' = "Bearer $token", 'host'="management.azure.com", 'Content-Type' = "application/json"}
This is good for getting an access token using the login dialog. I suspect that the Client ID for PowerShell could be used in the Oath2 call above.

Azure CDN - Custom Domain SSL via Resource Management API

Using the latest Azure Powershell SDK, but still can't seem to create Custom SSL Domains for CDNs in Azure via API Management. We have 100s of subdomains to create and need to be able to script the creation of this task for future extensibility.
Does anyone know how to toggle this flag via the REST API since the SDK has no support? We are using the New-AzureRmCdnCustomDomain commandlet.
Update: The AzureRM 6.13.0-module and the new Az-modules (including Az.Cdn) now supports this using a cmdlet. See Enable-AzureCdnCustomDomain (AzureRM.Cdn) or Enable-AzCdnCustomDomain (Az.Cdn)
The REST API for enabling Custom Domain HTTPS is documented at learn.microsoft.com
Enable Custom Https
Enable https delivery of the custom domain.
POST /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Cdn/profiles/{profileName}/endpoints/{endpointName}/customDomains/{customDomainName}/enableCustomHttps?api-version=2017-10-12
Before you can use the Azure REST API you need to get an access token:
Generating access token using PowerShell:
$Token = Invoke-RestMethod -Uri https://login.microsoftonline.com/<TenantID>/oauth2/token?api-version=1.0 -Method Post -Body #{
"grant_type" = "client_credentials"
"resource" = "https://management.core.windows.net/"
"client_id" = "<application id>"
"client_secret" = "<password you selected for authentication>"
}
The response contains an access token, information about how long that
token is valid, and information about what resource you can use that
token for. The access token you received in the previous HTTP call
must be passed in for all request to the Resource Manager API. You
pass it as a header value named "Authorization" with the value "Bearer
YOUR_ACCESS_TOKEN". Notice the space between "Bearer" and your access
token.
Client ID is retrived by creating an app registration in Azure AD and the clientkey is generated in the Keys-section of the created app registration. This can be combined into a solution like this:
$subscriptionId = "..."
$resourceGroupName = "..."
$profileName = "..."
$endpointName = "..."
$customDomainName = ".."
$Token = Invoke-RestMethod -Uri https://login.microsoftonline.com/<TenantID>/oauth2/token?api-version=1.0 -Method Post -Body #{
"grant_type" = "client_credentials"
"resource" = "https://management.core.windows.net/"
"client_id" = "<application id>"
"client_secret" = "<password you selected for authentication>"
}
$header = #{
"Authorization"= "Bearer $($Token.access_token)"
}
Invoke-RestMethod -Method Post -Headers $header -Uri "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Cdn/profiles/$profileName/endpoints/$endpointName/customDomains/$customDomainName/enableCustomHttps?api-version=2016-10-02"
If you don't need to automate the script, you can login manually using GUI (no need for app-registration) using this modified sample (based on Source). It requires AzureRM-module, which can be installed using Install-Module AzureRM:
Function Login-AzureRESTApi {
Import-Module AzureRM.Profile
# Load ADAL Azure AD Authentication Library Assemblies
$modulepath = Split-Path (Get-Module -Name AzureRM.Profile).Path
$adal = "$modulepath\Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
$adalforms = "$modulepath\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll"
$null = [System.Reflection.Assembly]::LoadFrom($adal)
$null = [System.Reflection.Assembly]::LoadFrom($adalforms)
# Login to Azure
$Env = Login-AzureRmAccount
# Select Subscription
$Subscription = (Get-AzureRmSubscription | Out-GridView -Title "Choose a subscription ..." -PassThru)
$adTenant = $Subscription.TenantId
$global:SubscriptionID = $Subscription.SubscriptionId
# Client ID for Azure PowerShell
$clientId = "1950a258-227b-4e31-a9cf-717495945fc2"
# Set redirect URI for Azure PowerShell
$redirectUri = "urn:ietf:wg:oauth:2.0:oob"
# Set Resource URI to Azure Service Management API | #marckean
$resourceAppIdURIASM = "https://management.core.windows.net/"
$resourceAppIdURIARM = "https://management.azure.com/"
# Set Authority to Azure AD Tenant
$authority = "https://login.windows.net/$adTenant"
# Create Authentication Context tied to Azure AD Tenant
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
# Acquire token
$global:authResultASM = $authContext.AcquireToken($resourceAppIdURIASM, $clientId, $redirectUri, "Auto")
$global:authResultARM = $authContext.AcquireToken($resourceAppIdURIARM, $clientId, $redirectUri, "Auto")
}
$resourceGroupName = "..."
$profileName = "..."
$endpointName = "..."
$customDomainName = ".."
Login-AzureRESTApi
#Reuse selected subscription from login
$Subscription = $global:subscriptionId
$header = #{
"Authorization"= $global:authResultARM.CreateAuthorizationHeader()
}
Invoke-RestMethod -Method Post -Headers $header -Uri "https://management.azure.com/subscriptions/$subscriptionId/resourceGroups/$resourceGroupName/providers/Microsoft.Cdn/profiles/$profileName/endpoints/$endpointName/customDomains/$customDomainName/enableCustomHttps?api-version=2017-10-12"