Error when trying to use Power BI REST API - Invoke-RestMethod : A positional parameter cannot be found that accepts argument - rest

Lately I've been trying to use Power BI REST API to make the refresh of a certain dataset automatically, by calling a .ps1 program. By following this tutorial, I was able to get this code, which is addapted as you can see below:
$groupID = "me" # the ID of the group that hosts the dataset. Use "me" if this is your My Workspace
$datasetID = "MYDATASETID" # the ID of the dataset that hosts the dataset
$clientId = "MYCLIENTID"
# Calls the Active Directory Authentication Library (ADAL) to authenticate against AAD
function GetAuthToken
{
if(-not (Get-Module AzureRm.Profile)) {
Import-Module AzureRm.Profile
}
$redirectUri = "urn:ietf:wg:oauth:2.0:oob"
$resourceAppIdURI = "https://analysis.windows.net/powerbi/api"
$authority = "https://login.microsoftonline.com/common/oauth2/authorize";
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
$authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId, $redirectUri, "Auto")
return $authResult
}
$token = GetAuthToken
$authHeader = #{
'Content-Type'='application/json'
'Authorization'=$token.CreateAuthorizationHeader()
}
$groupsPath = ""
if ($groupID -eq "me") {
$groupsPath = "myorg"
} else {
$groupsPath = "myorg/groups/$groupID"
}
$uri = "https://api.powerbi.com/v1.0/$groupsPath/datasets/$datasetID/refreshes"
Invoke-RestMethod -Uri $uri –Headers $authHeader –Method POST –Verbose
$uri = "https://api.powerbi.com/v1.0/$groupsPath/datasets/$datasetID/refreshes"
Invoke-RestMethod -Uri $uri –Headers $authHeader –Method GET –Verbose
I made sure to collect the parameters (groupID, clientID and datasetID) exactly as specified in the links above. However, when I try to execute this code, I get back the error:
Invoke-RestMethod : A positional parameter cannot be found that accepts argument 'â€Headers System.Collections.Hashtable â€Method'.
At C:\Users\me\Desktop:41 char:1
I can't quite tell what's going on, and I even found some similar cases, but none of the solutions worked for me. So, some help would be deeply appreciated.

It looks like this solution is copy/pasted from somewhere and the dashes are screwed up:
Delete the last 3 dashes in Invoke-RestMethod, which looks like dashes, but are other looks like dash unicode symbols, and replace them with normal "typed by the keyboard" ones.
Hope this helps!

Related

Get SPO sites using MS Graph API powershell not working

I'm trying to get all SharePoint Online sites' name and url via PowerShell using MS Graph API, but it's not seem to be working. That's all I get from the request:
#{#odata.context=https://graph.microsoft.com/v1.0/$metadata#sites; value=System.Object[]}
The application I use have all the needed Application type API permissions (Sites.Read, Sites.ReadWrite.All) with admin consent.
Do you have any idea why my script not working?
The code:
$TenantID = 'xxxxxxxxx.ONMICROSOFT.COM'
$ApplicationId = "xxxxx-xxxxxx-xxxx-xxxx"
$ApplicationSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$body = #{
'resource' = 'https://graph.microsoft.com'
'client_id' = $ApplicationId
'client_secret' = $ApplicationSecret
'grant_type' = "client_credentials"
'scope' = "openid"
}
$ClientToken = Invoke-RestMethod -Method post -Uri "https://login.microsoftonline.com/$($tenantid)/oauth2/token" -Body $body -ErrorAction Stop
$headers = #{ "Authorization" = "Bearer $($ClientToken.access_token)" }
$AllSites = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/sites?search=*" -Headers $headers -Method Get
Write-Host $AllSites
I've also tried these URIs:
https://graph.microsoft.com/v1.0/sites?search=*
https://graph.microsoft.com/v1.0/sites
https://graph.microsoft.com/v1.0/sites$select=siteCollection,webUrl&$filter=siteCollection/root%20ne%20null
The Write-Host cmdlet's primary purpose is to produce
for-(host)-display-only output, such as printing colored text like
when prompting the user for input in conjunction with Read-Host.
Write-Host uses the ToString() method to write the output. By
contrast, to output data to the pipeline, use Write-Output or implicit
output.
reference
This mean that your output is transformed for display purposes. Where you see System.Object[], there is actually data in there just waiting for you.
Based on your current results, your query look good.
Just do not use Write-Host and dig into the object as needed.
To get the site names, just use $AllSites.Value.Name
$AllSites = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/sites?search=*" -Headers $headers -Method Get
# Return site names
$AllSites.Value.Name
If you need to get additional information from each return you can loop into them, like this to do whatever you need. Here is a custom display of the site name along with an arbitrary index and the associated URL
$index = 0
# Will display results like
# 0: SiteName - www.contoso.sharepoint.com/SiteUrl
foreach ($Site in $AllSites.Value) {
Write-Host "$($index.ToString().PadRight(3,' ')): $($Site.Name) - " -NoNewline
Write-Host $site.webUrl -ForegroundColor Cyan
$index += 1
}
Also, here is an additional reference when working with Azure Graph API that will confirm your requests are working as expected: https://developer.microsoft.com/en-us/graph/graph-explorer

Using powershell to process emails in an o365 mailbox when connecting with application id and secret

I am writing a PowerShell script which reads through emails in an o365 email box.
It has to connect to the inbox, read through emails and make a decision based on the subject line, and then open those emails with a specific subject line and download any attachments the email might contain to a folder.
It [then] has to move the processed mail message to a mailbox sub-folder.
I have done this in the past using Exchange web services, and basic authentication to connect, but basic authentication is no longer available, and so I am having to re-do the script using modern authentication techniques.
I have been given an application id and secret from my Azure AD admin's with permissions for the relevant mailbox.
I've been googling away, and have managed to get some way using Microsoft Graph - using this section of Powershell:
$ClientID = "my-client-id"
$DirectoryID = "my-directory-id"
$ClientSecret = "my-client-secret"
$Credential = ConvertTo-GraphCredential -ClientID $ClientID -ClientSecret $ClientSecret -DirectoryID $DirectoryID
$mailbox = Get-EXOMailbox -UserPrincipalName myemailaccount#mycompany.com
This successfully gets me a mailbox object, but from here I am at a loss as to how to go about retrieving emails from it for processing.
The Microsoft doco and Google isn't helping me much at present on how extract emails from the mailbox having obtained the object.
Any thoughts/suggestions or pointers to relevant tutorials?
I managed to figure out how to access emails just usiing application id and secret without
using any non-standard libraries. You'll need to install the Graph and powershell utilities,
but they're free from Microsoft, so I call them standard. Then import them into my powershell
script thus:
Import-Module Microsoft.Graph.Mail
Import-Module Microsoft.PowerShell.Utility
After that, use these methods to access your o365 email system using the microsoft graph
REST API.
# This AuthenticateWithSecret is a slightly modified version of the method
# described by https://adamtheautomator.com/powershell-graph-api/
function Get-AccessToken
{
param(
[string] $AppId,
[string] $TenantName,
[string] $AppSecret)
$Scope = "https://graph.microsoft.com/.default"
$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"
# Add System.Web for urlencode
Add-Type -AssemblyName System.Web
# Create body
$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 the token!
$Request = Invoke-RestMethod #PostSplat
return $Request
}
# This method builds a header object that can be passed
# in with each request to the Graph REST API, for authentication
# purposes
function Get-Header
{
param($theRequest)
$tokenType = $theRequest.token_type
$accessToken = $theRequest.access_token
# Create header
$theHeader = #{
Authorization = "$($tokenType) $($accessToken)"
}
return $theHeader
}
# This method gets an object containing the email folders from
# a mailbox specified by its UserPrincipalName - which is typically
# the email address of the user concerned. By default it will return
# the first 200 folders it finds, but you can specify however many
# that you wish to return using the $numberOfFoldersToGet parameter.
function Get-Folders
{
param(
$Credential,
[string] $UserPrincipalName,
[int]$numberOfFoldersToGet=200)
$Header = Get-Header -theRequest $Credential
$restUrl = "https://graph.microsoft.com/v1.0/users/$UserPrincipalName/mailFolders/?`$top=$numberOfFoldersToGet"
$folderResult = Invoke-RestMethod -Uri $restUrl -Headers $Header -Method Get -ContentType "application/json"
return $folderResult
}
# This is a little helper function to get the specific folder object
# from an array of folder objects. You specify the folder that you
# want in the array based o its displayName using the $folderNameToFind
# parameter.
function Get-FolderFromListOfFolders
{
param($listOfFolders,
$folderNameToFind)
$specificFolder = ""
# Yeah, yeah, I know - we're doing this the brute-force way - just
# looping through all the folders until we find the one we want.
# Unless you have an insane number of folders, this shouldn't take
# *that* long, but this loop could be re-written to use a nicer search
# if it's really that big a problem.
foreach($fdr in $allFolders)
{
$thisFolderName = $fdr.displayName
if($thisFolderName -eq $folderNameToFind)
{
$specificFolder = $fdr
break
}
}
return $specificFolder
}
# This function allows you to retrieve an object describing a specific
# mail folder in an o365 Outlook, which you can specify by name. It allows
# you to access any folder by name - not just the common ones.
function Get-SpecificFolder
{
param(
$Credential,
[string] $UserPrincipalName,
[string] $folderName)
$allTheFolders = Get-Folders -Credential $Credential -UserPrincipalName $UserPrincipalName
$allFolders = $allTheFolders.value
$specificFolder = Get-FolderFromListOfFolders -listOfFolders $allFolders -folderNameToFind $folderName
$folderId = $specificFolder.id
$Header = Get-Header -theRequest $Credential
$theRestQuery = "https://graph.microsoft.com/v1.0/users/$UserPrincipalName/mailFolders/$folderId"
$folderResult = Invoke-RestMethod -Uri $theRestQuery -Headers $Header -Method Get -ContentType "application/json"
return $folderResult
}
# This function returns an object containing all the emails in a given
# Mail folder in Outlook in o365
function GetEmails
{
param(
$Credential,
[string] $UserPrincipalName,
[string] $folderId)
$Header = Get-Header -theRequest $Credential
$restUrl = "https://graph.microsoft.com/v1.0/users/$UserPrincipalName/mailFolders/$folderId/messages"
$emailResult = Invoke-RestMethod -Uri $restUrl -Headers $Header -Method Get -ContentType "application/json"
return $emailResult
}
You can use these methods in this way. First, you need to specify these variables
$ClientID = "My-azure-ad-client-id"
$DirectoryID = "My-directory-id"
$ClientSecret = "My-client-secret"
$MailboxName = "the-email-address-of-the-o365-mailbox-i-want-to-access"
$MailboxFolderName = "the-folder-name-of-the-mailfolder-containing-emails"
Then, you can get a credential object thus:
$Credential = Get-AccessToken -AppId $ClientID -TenantName $DirectoryID -AppSecret $ClientSecret
Then get your email folder object thus
$myfolder = Get-SpecificFolder -Credential $Credential -UserPrincipalName $MailboxName -folderName $MailboxFolderName
Then, get the id of your folder - this allows you to access any folder - even non-standard ones by name.
$folderId = $myfolder.id
Now, get the email objects from the folder
$emails = GetEmails -Credential $Credential -UserPrincipalName $MailboxName -folderId $folderId
Then get the actual array of emails
$theEmails = $emails.value
Now loop through your array of emails and do stuff with it.
foreach($email in $theEmails)
{
Write-Output $email.subject
}

how to authenticate Invoke-RestMethod to list artifactory repositories

Trying to get a list of Repositories from an Artifactory Enterprise v6 instance, using PowerShell 5.1 Invoke-RestMethod from Win10 desktop, but cannot see to get it to authenticate.
Seems simple enough, but this
$myCred = Get-Credential notStanley
$lstART = Invoke-RestMethod -URI https://<myserver>/artifactory/api/repositories -Credential $myCred
only returns the items that allow Anonymous access.
If I open a browser and logon to that Artifactory instance, I can then paste the above URI and get the full list of all repositories my account has access to.
Any hints what the $myCred is missing?
I have tried in the past with artifactory and -Credential doesnt really worked for me.
I tried the API way which is much simpler and easier to use.
Connecting to Artifactory with API Key
Read here to find out how you can get the API key for your account on artifactory.
$header = #{"X-JFrog-Art-Api" = "yourAPIKey"}
Invoke-RestMethod -URI https://<myserver>/artifactory/api/repositories -Headers $header
Using Basic Auth and -Credential
If you do want to work with the Get-Credential prompt, make sure you use the username that works in Artifactory. Its not the same as the domain\user. from here
$login = Get-Credential -Message "Enter Credentials for Artifactory"
#invalid creds.. but its ok. Need to tell invoke-restmethod to use Basic Auth.
$headers = #{ Authorization = "Basic Zm9vOmJhcg==" }
# use -Credential to override the credentials.
$new = Invoke-RestMethod -URI https://<server>/artifactory/api/repositories -Headers $headers -Credential $login
Thanks Jawad. That got me working with the API (my first try was not formed quite properly). Following your links found a couple other questions (27951561 and 60325084) that helped me get Credential as well. I have gone with Credential to avoid futzing with obfuscating an API Key in the source code.
My base skeleton now looks like:
# get standard PowerShell credential
$myCred = Get-Credential -Message "just <name> for this server, not '<domain>\<name>'"
# format credential for Artifactory API
$credUser = $myCred.UserName # extract user name
$credPswd = $myCred.GetNetworkCredential().password # extract user password
$credPair = "${credUser}:${credPswd}" # concatenate into BasicAuth format
$credBytes = [System.Text.Encoding]::ASCII.GetBytes($credPair) # convert byte values to text
$cred64 = [System.Convert]::ToBase64String($credBytes) # condense a bit more secure string RFC2045-MIME
$credAuth = "Basic $cred64" # intermediate formatting
$restHeaders = #{ Authorization = $credAuth } # initialize web headers
# clear collection array
$cfgSite = #()
# locate server
$lstURL "https://<myserver>/artifactory/api/repositories"
# get list of repositories
$theseRepo = Invoke-RestMethod -Headers $restHeaders -Uri $lstURL
# collect each configuration
ForEach ($thisRepo in $theseRepo)
{
$thisURI = $lstURL + $thisRepo.key
$thisCfg = Invoke-RestMethod -Headers $restHeaders -Uri $thisURI
$thisCfg | Add-Member -NotePropertyName "SITE" -NotePropertyValue "$thisSite"
$cfgSite += $thisCfg
}
# output to file
$cfgAll | Export-Csv .\lstArtRepoConf.csv -NoTypeInformation

Accessing Microsoft Graph With PowerShell

I've been having a hell of a time trying to access the Microsoft Graph using PowerShell.
First I tried using the authorization flow and Invoke-WebRequest and Invoke-RestMethod neither of which I could get to work.
Then I found this blog that showed how to do it using PowerShell and a couple of the Azure modules. Below is the code I'm using (ripped right from that blog) but every time I get to the Invoke-RestMethod (in the do-while loop) instead of getting the result of the query I get a 403 Forbidden error:
Invoke-RestMethod : The remote server returned an error: (403) Forbidden.
Function GetAuthToken {
Param (
[Parameter()]
$TenantName
)
Import-Module Azure
$clientId = "1950a258-227b-4e31-a9cf-717495945fc2"
$resourceAppIdURI = "https://graph.microsoft.com"
$authority = "https://login.microsoftonline.com/$TenantName"
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
$Credential = Get-Credential
$AADCredentialUser = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential" -ArgumentList $credential.UserName, $credential.Password
$authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId, $AADCredentialUser)
Write-Output $authResult
}
Function GetAllObjectOfType {
param
(
[Parameter(Mandatory = $true)]
$Tenant,
[Parameter(Mandatory = $true)]
$Type,
[Parameter(Mandatory = $false)]
$BatchSize = 100,
[Parameter(Mandatory = $false)]
$Version = 'Beta'
)
#------Get the authorization token------#
$token = GetAuthToken -TenantName $tenant
#------Building Rest Api header with authorization token------#
$authHeader = #{
'Content-Type' = 'application/json'
'Authorization' = $token.CreateAuthorizationHeader()
}
#------Initial URI Construction------#
#$uritest = "https://graph.microsoft.com/v1.0/users/user#contoso.com/mailFolders/Inbox/childFolders"
$uritest = "https://graph.microsoft.com/v1.0/me/mailFolders/Inbox"
#Join-Path -Path ''
$ObjCapture = #()
do {
$users = Invoke-RestMethod -Uri $uritest -Headers $authHeader -Method Get
$FoundUsers = ($Users.value).count
write-host "URI" $uri " | Found:" $FoundUsers
#------Batched URI Construction------#
$uri = $users.'#odata.nextlink'
$ObjCapture = $ObjCapture + $users.value
}until ($uri -eq $null)
$ObjCapture
}
I can run this same query (/v1.0/me/mailFolders/Inbox) from the Graph Explorer and it runs perfectly fine with no errors.
The GetAuthToken seems to be working as I do get a token back with an expiry, refresh token, etc and the $token.CreateAuthorizationHeader() also returns the correct Authorization = Bearer token
I've never done anything with the Microsoft Graph before so I'm sure there is something I'm doing wrong but I cannot for the life of me figure out what.
You cannot reuse the clientid from that blog post. You need to obtain your own clientid by registering your application. See Register your app with the Azure AD v2.0 endpoint for details on how to register your app.
This seems like it was answered in a follow-up blog post from the same source.
When an App is created, by default it has rights to access only the data of
the user that had signed in with the account though the “Sign in and read user
profile” delegated permissions. If you try to execute a script that uses this
AppID/ClientID to query Azure AD to get a list of all users in the directory,
you would receive an error stating (403) Forbidden because it didn’t have
adequate permissions to do that activity.
Putting this here in case any other wandering devs come across the post like I did. Here's the powershell function I'm using to get the access token from our tenant. ClientId and Secret are created in the Azure App Registrations, like others have mentioned.
$clientId = $args[0]
$secret = $args[1]
$redeemURI = "https://login.microsoftonline.com/{tenantGuid}/oauth2/v2.0/token"
$body = "client_id=$clientId&scope=https%3A%2F%2Fgraph.microsoft.com%2F.default&client_secret=$secret&grant_type=client_credentials"
$response = Invoke-RestMethod -Method Post -Uri $redeemURI -Body $body -ContentType "application/x-www-form-urlencoded"
return $response.access_token
Usage:
$token = ./GetAccessToken.ps1 "{clientId}" "{secret}"
I think it needs AD Azure Premium 2 licences
enter link description here

The Remote Server returned an error : (415) Unsupported media type for PowerBI Data Refresh API Call

I am trying to refresh power bi dataset using POST method with PowerShell script, but keep getting an error about media type so not sure what to do here. Just curious if somebody else having any solution for this. Thanks in advance for the help!!
Please see this source code for more details...
https://github.com/Azure-Samples/powerbi-powershell/blob/master/manageRefresh.ps1
# This sample script calls the Power BI API to progammtically trigger a refresh for the dataset
# It then calls the Power BI API to progammatically to get the refresh history for that dataset
# For full documentation on the REST APIs, see:
# https://msdn.microsoft.com/en-us/library/mt203551.aspx
# Instructions:
# 1. Install PowerShell (https://msdn.microsoft.com/en-us/powershell/scripting/setup/installing-windows-powershell) and the Azure PowerShell cmdlets (https://aka.ms/webpi-azps)
# 2. Set up a dataset for refresh in the Power BI service - make sure that the dataset can be
# updated successfully
# 3. Fill in the parameters below
# 4. Run the PowerShell script
# Parameters - fill these in before running the script!
# =====================================================
# An easy way to get group and dataset ID is to go to dataset settings and click on the dataset
# that you'd like to refresh. Once you do, the URL in the address bar will show the group ID and
# dataset ID, in the format:
# app.powerbi.com/groups/{groupID}/settings/datasets/{datasetID}
$groupID = " FILL ME IN " # the ID of the group that hosts the dataset. Use "me" if this is your My Workspace
$datasetID = " FILL ME IN " # the ID of the dataset that hosts the dataset
# AAD Client ID
# To get this, go to the following page and follow the steps to provision an app
# https://dev.powerbi.com/apps
# To get the sample to work, ensure that you have the following fields:
# App Type: Native app
# Redirect URL: urn:ietf:wg:oauth:2.0:oob
# Level of access: all dataset APIs
$clientId = " FILL ME IN "
# End Parameters =======================================
# Calls the Active Directory Authentication Library (ADAL) to authenticate against AAD
function GetAuthToken
{
$adal = "${env:ProgramFiles(x86)}\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Services\Microsoft.IdentityModel.Clients.ActiveDirectory.dll"
$adalforms = "${env:ProgramFiles(x86)}\Microsoft SDKs\Azure\PowerShell\ServiceManagement\Azure\Services\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll"
[System.Reflection.Assembly]::LoadFrom($adal) | Out-Null
[System.Reflection.Assembly]::LoadFrom($adalforms) | Out-Null
$redirectUri = "urn:ietf:wg:oauth:2.0:oob"
$resourceAppIdURI = "https://analysis.windows.net/powerbi/api"
$authority = "https://login.microsoftonline.com/common/oauth2/authorize";
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
$authResult = $authContext.AcquireToken($resourceAppIdURI, $clientId, $redirectUri, "Auto")
return $authResult
}
# Get the auth token from AAD
$token = GetAuthToken
# Building Rest API header with authorization token
$authHeader = #{
'Content-Type'='application/json'
'Authorization'=$token.CreateAuthorizationHeader()
}
# properly format groups path
$groupsPath = ""
if ($groupID -eq "me") {
$groupsPath = "myorg"
} else {
$groupsPath = "myorg/groups/$groupID"
}
# Refresh the dataset
$uri = "https://api.powerbi.com/v1.0/$groupsPath/datasets/$datasetID/refreshes"
Invoke-RestMethod -Uri $uri -Headers $authHeader -Method POST -Verbose
# Check the refresh history
$uri = "https://api.powerbi.com/v1.0/$groupsPath/datasets/$datasetID/refreshes"
Invoke-RestMethod -Uri $uri -Headers $authHeader -Method GET -Verbose
I've came across this exact issue.
You are basically trying to return refresh info from a dataset that cannot be refreshed (Eg. A direct Query dataset or the builtin metrics datasets)
You need to either add -ErrorAction SilentlyContinue
or wrap the dataset refresh API call in a loop like this:
$datasets = Invoke-RestMethod -Uri $uri -Headers $authHeader -Method GET
foreach($dataset in $datasets.value)
{
if($dataset.isRefreshable -eq $true)
{
#Build API String
$uri2 = "https://api.powerbi.com/v1.0/$groupsPath/datasets/$($dataset.id)/refreshes"
#Return refresh info for each dataset
$refreshes = Invoke-RestMethod -Uri $uri2 -Headers $authHeader -Method GET
}
}