Forum Discussion

Jacqui Hurst's avatar
Jacqui Hurst
Copper Contributor
Feb 20, 2024

Invoke-RestMethod Upload a File to Sharepoint 401 error

Hi

 

Please help.  I have tried many different scripts (taken from the Internet and modified)  to try and upload a file to a Sharepoint library but I keep leading myself back to the same issue of a 401 error, access denied.  The only errors I have in the script are when I use the invoke-restmethod.  This occurs on both instances in the script.

 

One of the scripts is as follows

 

# Set your SharePoint site URL and library name
$libraryName = "Shared Documents"
 
 
# Authenticate to Microsoft Graph using the app registration credentials
$clientId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$tenantId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$clientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxx"
 
# Get an access token
$body = @{
    client_id     = $clientId
    scope         = "https://graph.microsoft.com/.default"
    client_secret = $clientSecret
    grant_type    = "client_credentials"
}
$response = Invoke-RestMethod -Uri $tokenUrl -Method POST -ContentType "application/x-www-form-urlencoded" -Body $body
$accessToken = $response.access_token
 
# Create a new folder in the library based on the current date
$folderName = (Get-Date).ToString("yyyyMMdd")
$folderUrl = "$siteUrl/_api/web/lists/getbytitle('$libraryName')/rootfolder/folders/add(url='$folderName')"
 
 
try {
 
    Invoke-RestMethod -Uri $folderUrl -Method POST -Headers @{Authorization = "Bearer  $accessToken"
}
} catch {
    # Dig into the exception to get the Response details.
    Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
    Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
}
 
 
# Upload a file (modify this part according to your actual data source)
$filePath = "C:\TEMP\Myfilesourcename.csv"
$fileName = "FileName.csv"
$fileUrl = "$siteUrl/_api/web/lists/getbytitle('$libraryName')/rootfolder/files/add(url='$fileName',overwrite=true)"
 
try {
    Invoke-RestMethod -Uri $fileUrl -Method POST -Headers @{Authorization = "Bearer  $accessToken"} -InFile $filePath
} catch {
    # Dig into the exception to get the Response details.
    Write-Host "StatusCode:" $_.Exception.Response.StatusCode.value__
    Write-Host "StatusDescription:" $_.Exception.Response.StatusDescription
}
 
I have granted the Application Sharepoint - Sites.Read.All and Site.Read.Write.All and Microsoft Graph - Sites.Read.All and Sites.ReadWrite.All with the API permissions.  These have been granted at the admin level.  I have checked all my clientsecret etc are correct and I get an access token.
 
I am at a loss as to what I am missing about granting the correct access or applying my token correctly in my script.
 
Can anyone help?
 
Thanks
 
 
  • Jacqui Hurst's avatar
    Jacqui Hurst
    Mar 01, 2024
    I took a step back and started again and after a little more research I found a method that works. I moved away from Client Secret to a certificate and used PNP.Powershell. There is loads of work in getting this running but at least I have an unattended script now.

    $tenant = “xxxxxxx”
    $url = "https://$tenant.sharepoint.com/sites/ITandSoftware"
    $file = "c:\temp\test.txt"
    $ClientID = "xxxxxxxxxxxxxx"
    $CertThumb = 'xxxxxxxxxxxxxxxxxx'

    Connect-PnPOnline $url -ClientId $ClientID -Tenant $tenant.onmicrosoft.com -Thumbprint $CertThumb

    Add-PnPFile -folder "Shared Documents/Calls" -path $file


    I thought I would share in case anyone wanted to know the conclusion.

    Thanks for responses though, it helps to know there is some help out there when needed.
  • LainRobertson's avatar
    LainRobertson
    Silver Contributor

    Jacqui Hurst 

     

    Hi, Jacqui.

     

    I'm not a SharePoint person, so I could be speaking out of turn here, but in line with the following documentation, have you considered setting your $siteUrl variable to use the recommended Graph URLs rather than your current value, which is based on the SharePoint URLs?

     

     

    For example, something like (or the beta endpoint equivalent):

     

     https://graph.microsoft.com/v1.0/sites/ITandSoftware

     

    Rather than:

     

    https://mytenant.sharepoint.com/sites/ITandSoftware

     

    If I've interpreted your starting position correctly, you're successfully obtaining a Microsoft Graph token, so I'm assuming it's not the initial Invoke-RestMethod you're having trouble with, but rather the ones afterwards.

     

    Cheers,

    Lain

    • Jacqui Hurst's avatar
      Jacqui Hurst
      Copper Contributor

      Hi LainRobertson 

       

      Thanks for your reply much appreciated.  I think you are pointing me in the right direction.  I'm no developer and Graph API is totally new to me as is running stuff on Azure hosted machines so I'm on  steep learning curve currently.

       

      I've tried your suggestion but that has not fixed it but I'm sure its along these lines with regard to the URI I am providing.

       

      Neither of the invoke-restmethod commands work, both are access denied. I am assuming I am getting a token as my $accesstoken is populated.  I'm also wondering if my access token is not for what I want to access, again a URI issue.

       

      I'm going to carry on looking but thanks for your suggestions.  If there are any other pointers happy to take these.

       

      Jacqui

      • LainRobertson's avatar
        LainRobertson
        Silver Contributor

        Jacqui Hurst 

         

        Hi, Jacqui.

         

        There's not a lot I can do given I don't use SharePoint, but I took a look at the servicePrincipal permissions side of things, as that's the one thing I can comment on.

         

        To check the permissions, you need to log into the Azure Portal at https://portal.azure.com.

         

        Once you're in there, you want to navigate to "Microsoft Entra ID".

         

        Looking over the left-hand side, you will see App registrations listed in the index, which if you click on, you should see a heading in the middle section titled Owned applications (which is another term for servicePrincipal), under which you should find your servicePrincipal listed.

         

         

        Once you've located it, clicking on your servicePrincipal will shown a new screen, where on the left-hand side, you will see API permissions, and if you click on that, you see a list of permissions already granted to the servicePrincipal.

         

        The key column to focus on here is Type, where the correct type is Application. If you see any permissions listed as Delegated, that's the wrong type for a servicePrincipal and won't work.

         

         

        In my screenshot, you can see I've already added the SharePoint Sites.Read.All, however, if you do not see this - or it's the wrong type - then click the Add a permission heading, navigate through the default list and choose SharePoint and then choose Application permissions (not Delegated permissions!)

         

        You may need to add more than just Sites.Read.All, as I only added that single entry so I could test successfully getting past the HTTP 401 response (which I've done, and have removed that permission from the servicePrincipal).

         

        This brings you to the SharePoint application permissions page where you can then enable the required permissions (don't assign higher permissions than necessary, as that's a big security no-no!)

         

         

        Once you've added the new permission(s), you then have to go one step further and provide final approval for the new permission using the Grant admin consent for <tenant> button (features on the previous screen):

         

         

        You'll know you're done when you see all green circles with white ticks - as shown above. If you see warning triangle(s) then you haven't yet finished with the admin approval process.

         

        It can take a little while for the new access to kick in, so perhaps go do something for a while before coming back, opening a new PowerShell window and trying again.

         

        On the PowerShell code side, one small difference I have in some of my modules is that I'm using the "v2.0" authentication endpoint. I did test your existing endpoint and that did work, so I only mention it as a curiosity - I'm not suggesting you need to do as I have done (for all I know, one is an alias to the other!)

         

         

        $tokenUrl = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token";

         

         

        Anyhow, this only addresses the early aspects of your script and potentially the HTTP 401. Unfortunately, I cannot test the rest as I don't have any SharePoint content to test against.

         

        Cheers,

        Lain

Resources