0

I am writing a Powershell script which will upload an Excel document to Atlassian Confluence page via REST API, using token authentication.

Write-Host "Start of script"

$userToken = "TOKEN"
$pageId = "PAGE-ID"
$fileName = "All_Employee_List.xlsx"

$rootFolder = "C:\moonfall\Powershell\Server Side\General Server Scripts"
$subFolder = "All_Employee_list\All_Employee_List_SRV_2"
$filePath = Join-Path $rootFolder $subFolder


$wpURL = "https://DOMAIN/rest/api/content/"

$Headers = @{
    'Authorization' = "Bearer $userToken"
    'X-Atlassian-Token' = 'nocheck'
}

# Get the attachment ID if it exists
$uri = $wpURL + $pageId + "/child/attachment?filename=$fileName&expand=body.storage,version,space,ancestors"
$attachment = Invoke-RestMethod -Method GET -Headers $Headers -Uri $uri
$attachmentId = $attachment.results.id

# If the attachment doesn't exist, create it
if (!$attachmentId) {
    $uri = $wpURL + $pageId + "/child/attachment"
    
    #after this file path check it won't upload to confluence anymore, it just says access to the path is denied
    if (-not (Test-Path $filePath)) {
        Write-Error "File does not exist at $filePath"
        return
    }
    
    try {
        $fileBytes = [System.IO.File]::ReadAllBytes(${filePath})
    } catch {
        Write-Error "Failed to read file at ${filePath}: $_"
        return
    }

    $fileEncoded = [System.Convert]::ToBase64String($fileBytes)

    $delimiter = [System.Guid]::NewGuid().ToString()
    $LF = "`r`n"
    $bodyData = ( 
        "--$delimiter",
        "Content-Disposition: form-data; name=`"file`"; filename=`"$fileName`"",
        "Content-Type: application/octet-stream$LF",
        $fileEncoded,
        "--$delimiter--$LF" 
    ) -join $LF

    Invoke-RestMethod -Uri $uri -Method POST -ContentType "multipart/form-data; boundary=$delimiter" -Headers $Headers -Body $bodyData
    Write-Output "Attachment created successfully."
} else {
    Write-Output "Attachment already exists. Skipping creation."
}

# Set the attachment as the primary viewable attachment
$uri = $wpURL + $attachmentId + "/?minorEdit=true"
$bodyData = @{
    "id" = $attachmentId
    "version" = @{
        "number" = $attachment.version.number + 1
    }
    "type" = "attachment"
    "title" = $fileName
    "container" = @{
        "id" = $pageId
        "type" = "page"
    }
    "metadata" = @{
        "comment" = @{
            "value" = "Automatically uploaded from PowerShell script."
        }
        "labels" = @()
        "properties" = @{
            "download" = @{
                "value" = @{
                    "downloadAll" = "true"
                }
            }
        }
    }
    "extensions" = @{
        "mediaType" = @{
            "value" = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
            "macroEnabled" = "true"
        }
    }
    "status" = "current"
} | ConvertTo-Json

# Make the request
$response = Invoke-RestMethod -Uri $uri -Method POST -Headers $Headers -ContentType "multipart/form-data; boundary=$delimiter" -Body $bodyData

# Print the response
$response

When I run the script in Powershell terminal with admin privileges I get the error:

Failed to read file at 'FILE-PATH': Exception calling "ReadAllBytes" with "1" argument(s): "Access to the path 'FILE-PATH' is denied.

But when I remove the file path check of $filebytes it writes a slightly different error:

Exception calling "ReadAllBytes" with "1" argument(s): "Access to the path 'C:\moonfall\Powershell\Server Side\General Server Scripts\All_Employee_list\All_Employee_List_SRV_2' is denied."
At line:29 char:5
+     $fileBytes = [System.IO.File]::ReadAllBytes($filePath)
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : UnauthorizedAccessException
 
Exception calling "ToBase64String" with "1" argument(s): "Value cannot be null.
Parameter name: inArray"
At line:30 char:5
+     $fileEncoded = [System.Convert]::ToBase64String($fileBytes)
+     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ArgumentNullException


results                                                                                                                                               size _links
-------                                                                                                                                               ---- ------
{@{id=123455572; type=attachment; status=current; title=All_Employee_List.xlsx; version=; container=; metadata=; extensions=; _links=; _expandable=}}    1 @{base=https://DOMAIN; context=}
Attachment created successfully.
Invoke-RestMethod : The remote server returned an error: (415).
At line:84 char:13
+ $response = Invoke-RestMethod -Uri $uri -Method POST -Headers $Header ...
+             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand

And uploads the file as attachment to the page but the file has 0.0 kb and is nothing inside when opened. So as I see it I have two problems with this script. Please if anyone could help, this is all very much new to me and been trying to solve this for 3 weeks now and also with the help of ChatGPT.

r4ymaster
  • 1
  • 1
  • The 2nd error also gives an "access denied" error. There is no code which can fix this, you need to verify that the user account which runs the script can actually access the file in the specified location (and also make sure that the file is not locked by another application) – bluuf Mar 17 '23 at 16:35
  • The user is admin on the machine, location is accessible and the file has security rights set to all for all users. That's why I'm so confused by this error. – r4ymaster Mar 17 '23 at 17:25

1 Answers1

0

You missed adding the file name to the file path:

$fileName = "All_Employee_List.xlsx"

$rootFolder = "C:\moonfall\Powershell\Server Side\General Server Scripts"
$subFolder = "All_Employee_list\All_Employee_List_SRV_2"
$filePath = Join-Path $rootFolder $subFolder ## File name missing?

So ReadAllBytes(${filePath}) tries to read the folder and chokes. The error message is not really accurate

Cpt.Whale
  • 4,784
  • 1
  • 10
  • 16
  • @r4ymaster `[System.IO.File]::ReadAllBytes()` needs a full path to a specific file. Naming convention is up to you, but maybe you want `$fileBytes = [System.IO.File]::ReadAllBytes((Join-Path $filePath $fileName))`. You can see that the "Access to the path ... is denied" error doesn't contain `All_Employee_List.xlsx` at all – Cpt.Whale Mar 17 '23 at 18:07
  • Thank you so so much, this actually worked! Now I have another issue that the error returned is: Invoke-RestMethod : The remote server returned an error: (415) The 415 error code is an indication that your REST call is using the incorrect 'Content-type' header for the file you are trying to upload. File is uploaded and has correct file size in Confluence but is corrupted. – r4ymaster Mar 17 '23 at 18:14
  • @r4ymaster Glad to hear! Consider marking it as the answer. The `$bodyData` for your POST request seems like stuff that should be in the header/ContentType parameters. I usually would only have`$fileBytes` in `-Body`, but I don't know what Confluence requires – Cpt.Whale Mar 17 '23 at 18:46