1

I am trying to back up the BigIP F5 device using RestAPI/PowerShell and I can't figure out how to download a "Partial Content"-based file.

I can grab the 1st chunk using:

$result = Invoke-WebRequest -Uri "https://$server/mgmt/shared/file-transfer/ucs-downloads/RomanTest.UCS" -Method Get -Headers $headers

Then I need to grab all the other chunks, right?

So, I am looking for the total file size:

$fileLength = ($result.Headers."Content-Range" -split "/")[-1]

Then I am trying to save it but I can't understand what should I put in the $file.Write?

$index = 0; $start = 0; $end = 0
$maxloops = [Math]::Round([Math]::Ceiling($fileLength / $partSizeBytes))

$fileName = "C:\Temp\Roman\RomanTest$(Get-Date -Format yyMMdd_HHmmss).ucs"
$file = [System.IO.FileStream]::new($fileName, [System.IO.FileMode]::Create)



    while ($fileLength -gt ($end + 1)) 
    {
        $start = $index * $partSizeBytes
        if (($start + $partSizeBytes - 1 ) -lt $fileLength) 
        {
            $end = ($start + $partSizeBytes - 1)
        }
        else 
        {
            $end = ($start + ($fileLength - ($index * $partSizeBytes)) - 1)
        }

        
        $headers = @{    
            "Content-Type" = "application/json"
            "X-F5-Auth-Token" = "$token"
            'Content-Range' = "$start-$end/$fileLength"
        }

        Write-Host "Bytes $start-$end/$fileLength | Index: $($index.ToString("000")) and ChunkSize: $partSizeBytes"

        $result = Invoke-WebRequest -Uri "https://$server/mgmt/shared/file-transfer/ucs-downloads/RomanTest.UCS" -Method Get -Headers $headers
        [byte[]]$data = $result.Content

        Write-Host "$start - $($end-$start)" 

        $file.Write($data, ?????, ?????)
        $file.Close()       
    
        $index++
    
        Write-Host "Percentage Complete: $([Math]::Ceiling($index/$maxloops*100)) %"
    }
$file.Close()

Any help appreciated

  • 1
    The two ```?????``` parameters are ```offset``` and ```count``` respectively. See the documentation at https://learn.microsoft.com/en-us/dotnet/api/system.io.filestream.write?view=net-7.0#system-io-filestream-write(system-byte()-system-int32-system-int32) for descriptions... – mclayton May 11 '23 at 20:07
  • 1
    OK, so it looks like the 1st parameter is 0, but the 2nd one is $data.length? – Roman Melekh May 11 '23 at 20:30

1 Answers1

1

Okay, it looks like this is the way to solve the problem, any comments are welcome!

function Start-F5ArchiveDownload
{
    param 
    (
        [Parameter(Mandatory = $true)][string]$token,        
        [Parameter(Mandatory = $true)][string]$server,
        [Parameter(Mandatory = $true)][string]$UCSFileName,        
        [Parameter(Mandatory = $true)][string]$LocalFileName
    )
    
    $headers = @{
        "Content-Type" = "application/json"
        "X-F5-Auth-Token" = "$token"
    }

    $result = Invoke-WebRequest -Uri "https://$server/mgmt/shared/file-transfer/ucs-downloads/$UCSFileName" -Method Get -Headers $headers

    if ($result.StatusCode -eq "206" -and $result.StatusDescription -eq "Partial Content")
    {
        Write-Host "This is a big file, will be transferred by chunks"
        [int]$fileLength = ($result.Headers."Content-Range" -split "/")[-1]
        Write-Host File size: $($fileLength.ToString("#,#"))
    }

    $partSizeBytes = 1048575 
    $index = 0; $start = 0; $end = 0
    
    $maxloops = [Math]::Round([Math]::Ceiling($fileLength / $partSizeBytes))

    $file = [System.IO.FileStream]::new($LocalFileName, [System.IO.FileMode]::Append)

    while ($fileLength -gt ($end + 1)) 
    {
        $start = $index * $partSizeBytes
        if (($start + $partSizeBytes - 1 ) -lt $fileLength) 
        {
            $end = ($start + $partSizeBytes - 1)
        }
        else 
        {
            $end = ($start + ($fileLength - ($index * $partSizeBytes)) - 1)
        }
        
        $headers = @{    
            "Content-Type" = "application/json"
            "X-F5-Auth-Token" = "$token"
            "Content-Range" = "$start-$end/$fileLength"
        }

        Write-Host "Bytes $start-$end/$fileLength | Index: $($index.ToString("000")) and ChunkSize: $partSizeBytes"

        $result = Invoke-WebRequest -Uri "https://$server/mgmt/shared/file-transfer/ucs-downloads/$UCSFileName" -Method Get -Headers $headers
        
        [byte[]]$data = $result.Content

        Write-Host "$start - $($end-$start)" 

        $file.Write($data, 0, $($result.Content.LongLength))
            
        $index++
    
        Write-Host "Percentage Complete: $([Math]::Ceiling($index/$maxloops*100)) %"
    }

    $file.Close()
}
  • I was wrong: the `offset` parameter doesn't refer to the _file_, but to the _input byte array_. Therefore, `$file.Write($data, 0, $data.Count)` or the equivalent `$file.Write($data, 0, $data.Length)` will do - you only need `.LongLength` for _multi-dimensional_ arrays, which are not involved here. – mklement0 May 11 '23 at 22:08
  • In other words: the answer to your first comment on the question is: yes. As an aside: In an _expression_ - which a method call by definition is - you never need `$(...)` to enclose another expression. To use an expression as a _command argument_, it is usually sufficient - and preferable - to use `(...)` rather than `$(...)` - see [this answer](https://stackoverflow.com/a/58248195/45375). – mklement0 May 11 '23 at 22:12
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community May 12 '23 at 00:21