Public/DBFSAPI.ps1

#requires -Version 3.0
Function Add-FSFile
{
    <#
            .SYNOPSIS
            Opens a stream to write to a file and returns a handle to this stream. There is a 10 minute idle timeout on this handle. If a file or directory already exists on the given path and overwrite is set to false, this call will throw an exception with RESOURCE_ALREADY_EXISTS. A typical workflow for file upload would be:
            .DESCRIPTION
            Opens a stream to write to a file and returns a handle to this stream. There is a 10 minute idle timeout on this handle. If a file or directory already exists on the given path and overwrite is set to false, this call will throw an exception with RESOURCE_ALREADY_EXISTS. A typical workflow for file upload would be:
            Official API Documentation: https://docs.databricks.com/api/latest/dbfs.html#create
            .PARAMETER Path
            The path of the new file. The path should be the absolute DBFS path (e.g. "/mnt/foo.txt"). This field is required.
            .PARAMETER Overwrite
            The flag that specifies whether to overwrite existing file/files.
            .EXAMPLE
            $newFile = Add-DatabricksFSFile -Path "/myTestFolder/myFile1.txt" -Overwrite $true
            Close-DatabricksFSFile -Handle $newFile.handle
            .EXAMPLE
            #AUTOMATED_TEST:Add empty file
            $newFile = Add-DatabricksFSFile -Path "/myTestFolder/myFile1.txt" -Overwrite $true
            Close-DatabricksFSFile -Handle $newFile.handle
            .EXAMPLE
            #AUTOMATED_TEST:Add new file with content and close it
            $newFile = Add-DatabricksFSFile -Path "/myTestFolder/myFile2.txt" -Overwrite $true
            Add-DatabricksFSFileBlock -Handle $newFile.handle -Data "This is a plaintext!" -AsPlainText
            Close-DatabricksFSFile -Handle $newFile.handle
    #>

    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)] [string] $Path, 
        [Parameter(Mandatory = $false, Position = 2)] [bool] $Overwrite = $false
    )
    
    $requestMethod = "POST"
    $apiEndpoint = "/2.0/dbfs/create"

    Write-Verbose "Building Body/Parameters for final API call ..."
    #Set parameters
    $parameters = @{
        path = $Path 
        overwrite = $Overwrite 
    }
    
    $result = Invoke-ApiRequest -Method $requestMethod -EndPoint $apiEndpoint -Body $parameters

    return $result
}

Function Add-FSFileBlock
{
    <#
            .SYNOPSIS
            Appends a block of data to the stream specified by the input handle. If the handle does not exist, this call will throw an exception with RESOURCE_DOES_NOT_EXIST. If the block of data exceeds 1 MB, this call will throw an exception with MAX_BLOCK_SIZE_EXCEEDED.
            .DESCRIPTION
            Appends a block of data to the stream specified by the input handle. If the handle does not exist, this call will throw an exception with RESOURCE_DOES_NOT_EXIST. If the block of data exceeds 1 MB, this call will throw an exception with MAX_BLOCK_SIZE_EXCEEDED.
            Official API Documentation: https://docs.databricks.com/api/latest/dbfs.html#add-block
            .PARAMETER Handle
            The handle on an open stream. This field is required.
            .PARAMETER Data
            The base64-encoded data to append to the stream. This has a limit of 1 MB. This field is required.
            .PARAMETER AsPlainText
            If specified, Data is interpreted as plain text and encoded to Base64 internally before the upload.
            .EXAMPLE
            Add-DatabricksFSFileBlock -Handle 7904256 -Data "ZGF0YWJyaWNrcwo="
            #AUTOMATED_TEST:Add new file with content and close it
            $newFile = Add-DatabricksFSFile -Path "/myTestFolder/myFile2.txt" -Overwrite $true
            Add-DatabricksFSFileBlock -Handle $newFile.handle -Data "This is a plaintext!" -AsPlainText
            Close-DatabricksFSFile -Handle $newFile.handle
    #>

    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)] [int] $Handle, 
        [Parameter(Mandatory = $true, Position = 2)] [string] $Data,
        [Parameter(Mandatory = $false, Position = 2)] [switch] $AsPlainText
    )
    
    $requestMethod = "POST"
    $apiEndpoint = "/2.0/dbfs/add-block"

    if($AsPlainText)
    {
        $Data = $Data | ConvertTo-Base64 -Encoding ([Text.Encoding]::UTF8)
    }
    
    Write-Verbose "Building Body/Parameters for final API call ..."
    #Set parameters
    $parameters = @{
        handle = $Handle 
        data = $Data 
    }
    
    $result = Invoke-ApiRequest -Method $requestMethod -EndPoint $apiEndpoint -Body $parameters

    return $result
}

Function Close-FSFile
{
    <#
            .SYNOPSIS
            Closes the stream specified by the input handle. If the handle does not exist, this call will throw an exception with RESOURCE_DOES_NOT_EXIST.
            .DESCRIPTION
            Closes the stream specified by the input handle. If the handle does not exist, this call will throw an exception with RESOURCE_DOES_NOT_EXIST.
            Official API Documentation: https://docs.databricks.com/api/latest/dbfs.html#close
            .PARAMETER Handle
            The handle on an open stream. This field is required.
            .EXAMPLE
            Close-DatabricksFSFile -Handle 7904256
            #AUTOMATED_TEST:Add and close empty file
            $newFile = Add-DatabricksFSFile -Path "/myTestFolder/myFile1.txt" -Overwrite $true
            Close-DatabricksFSFile -Handle $newFile.handle
            .EXAMPLE
            #AUTOMATED_TEST:Add new file with content and close it
            $newFile = Add-DatabricksFSFile -Path "/myTestFolder/myFile2.txt" -Overwrite $true
            Add-DatabricksFSFileBlock -Handle $newFile.handle -Data "This is a plaintext!" -AsPlainText
            Close-DatabricksFSFile -Handle $newFile.handle
    #>

    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)] [int] $Handle
    )
    
    $requestMethod = "POST"
    $apiEndpoint = "/2.0/dbfs/close"

    Write-Verbose "Building Body/Parameters for final API call ..."
    #Set parameters
    $parameters = @{
        handle = $Handle 
    }
    
    $result = Invoke-ApiRequest -Method $requestMethod -EndPoint $apiEndpoint -Body $parameters

    return $result
}

Function Remove-FSItem
{
    <#
            .SYNOPSIS
            Delete the file or directory (optionally recursively delete all files in the directory). This call will throw an exception with IO_ERROR if the path is a non-empty directory and recursive is set to false or on other similar errors.
            .DESCRIPTION
            Delete the file or directory (optionally recursively delete all files in the directory). This call will throw an exception with IO_ERROR if the path is a non-empty directory and recursive is set to false or on other similar errors.
            Official API Documentation: https://docs.databricks.com/api/latest/dbfs.html#delete
            .PARAMETER Path
            The path of the file or directory to delete. The path should be the absolute DBFS path (e.g. "/mnt/foo/"). This field is required.
            .PARAMETER Recursive
            Whether or not to recursively delete the directory's contents. Deleting empty directories can be done without providing the recursive flag.
            .EXAMPLE
            Remove-DatabricksFSItem -Path "/MyFolder" -Recursive $false
            .EXAMPLE
            #AUTOMATED_TEST:Add and remove File
            $filePath = "/myTestFolder/myFile1.txt"
            $newFile = Add-DatabricksFSFile -Path $filePath -Overwrite $true
            Close-DatabricksFSFile -Handle $newFile.handle
            Remove-DatabricksFSItem -Path $filePath
            .EXAMPLE
            #AUTOMATED_TEST:Add and remove folder
            $folderPath = "/myTestFolder/myFolder"
            Add-DatabricksFSDirectory -Path $folderPath
            Remove-DatabricksFSItem -Path $folderPath
    #>

    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)] [string] $Path, 
        [Parameter(Mandatory = $false, Position = 2)] [bool] $Recursive = $false
    )
    
    $requestMethod = "POST"
    $apiEndpoint = "/2.0/dbfs/delete"

    Write-Verbose "Building Body/Parameters for final API call ..."
    #Set parameters
    $parameters = @{
        path = $Path 
        recursive = $Recursive 
    }
    
    $result = Invoke-ApiRequest -Method $requestMethod -EndPoint $apiEndpoint -Body $parameters

    return $result
}

Function Get-FSItem
{
    <#
            .SYNOPSIS
            Gets the file information of a file or directory. If the file or directory does not exist, this call will throw an exception with RESOURCE_DOES_NOT_EXIST.
            .DESCRIPTION
            Gets the file information of a file or directory. If the file or directory does not exist, this call will throw an exception with RESOURCE_DOES_NOT_EXIST.
            Official API Documentation: https://docs.databricks.com/api/latest/dbfs.html#get-status
            .PARAMETER Path
            The path of the file or directory. The path should be the absolute DBFS path (e.g. "/mnt/foo/"). This field is required.
            .PARAMETER ChildItems
            Defines whether information of the item or its child items are returned. This field is not required. Default is 'false'.
            .EXAMPLE
            Get-DatabricksFSItem -Path "/myFolder/myFile"
            .EXAMPLE
            #AUTOMATED_TEST:Get single file
            $filePath = "/myTestFolder/myFile1.txt"
            $newFile = Add-DatabricksFSFile -Path $filePath -Overwrite $true
            Close-DatabricksFSFile -Handle $newFile.handle
            Get-DatabricksFSItem -Path $filePath
            .EXAMPLE
            #AUTOMATED_TEST:Get single folder
            $folderPath = "/myTestFolder/"
            Get-DatabricksFSItem -Path $folderPath
            .EXAMPLE
            #AUTOMATED_TEST:Add and remove folder
            $folderPath = "/myTestFolder/"
            Add-DatabricksFSDirectory -Path $folderPath
            Get-DatabricksFSItem -Path $folderPath -ChildItems
    #>

    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)] [string] $Path,
        [Parameter(Mandatory = $false, Position = 2)] [switch] $ChildItems
    )
    
    $requestMethod = "GET"
    $apiEndpoint = "/2.0/dbfs/get-status"
    if($ChildItems)
    {
        $apiEndpoint = "/2.0/dbfs/list"
    }
        

    Write-Verbose "Building Body/Parameters for final API call ..."
    #Set parameters
    $parameters = @{
        path = $Path 
    }
    
    $result = Invoke-ApiRequest -Method $requestMethod -EndPoint $apiEndpoint -Body $parameters

    if($ChildItems){
        # if -ChildItems was specified, we return the files as an array
        return $result.files
    }
    else
    {
        # if -ChildItems was not specified, we return the result as it is (a single file)
        return $result
    }
}

Function Add-FSDirectory
{
    <#
            .SYNOPSIS
            Creates the given directory and necessary parent directories if they do not exist. If there exists a file (not a directory) at any prefix of the input path, this call will throw an exception with RESOURCE_ALREADY_EXISTS. Note that if this operation fails it may have succeeded in creating some of the necessary parent directories.
            .DESCRIPTION
            Creates the given directory and necessary parent directories if they do not exist. If there exists a file (not a directory) at any prefix of the input path, this call will throw an exception with RESOURCE_ALREADY_EXISTS. Note that if this operation fails it may have succeeded in creating some of the necessary parent directories.
            Official API Documentation: https://docs.databricks.com/api/latest/dbfs.html#mkdirs
            .PARAMETER Path
            The path of the new directory. The path should be the absolute DBFS path (e.g. "/mnt/foo/"). This field is required.
            .EXAMPLE
            Add-DatabricksFSDirectory -Path "/myNewFolder"
            .EXAMPLE
            #AUTOMATED_TEST:Add a folder
            $folderPath = "/myTestFolder/myFolder2"
            Add-DatabricksFSDirectory -Path $folderPath
    #>

    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)] [string] $Path
    )
    
    $requestMethod = "POST"
    $apiEndpoint = "/2.0/dbfs/mkdirs"

    Write-Verbose "Building Body/Parameters for final API call ..."
    #Set parameters
    $parameters = @{
        path = $Path 
    }
    
    $result = Invoke-ApiRequest -Method $requestMethod -EndPoint $apiEndpoint -Body $parameters

    return $result
}

Function Move-FSItem
{
    <#
            .SYNOPSIS
            Move a file from one location to another location within DBFS. If the source file does not exist, this call will throw an exception with RESOURCE_DOES_NOT_EXIST. If there already exists a file in the destination path, this call will throw an exception with RESOURCE_ALREADY_EXISTS. If the given source path is a directory, this call will always recursively move all files.
            .DESCRIPTION
            Move a file from one location to another location within DBFS. If the source file does not exist, this call will throw an exception with RESOURCE_DOES_NOT_EXIST. If there already exists a file in the destination path, this call will throw an exception with RESOURCE_ALREADY_EXISTS. If the given source path is a directory, this call will always recursively move all files.
            Official API Documentation: https://docs.databricks.com/api/latest/dbfs.html#move
            .PARAMETER SourcePath
            The source path of the file or directory. The path should be the absolute DBFS path (e.g. "/mnt/foo/"). This field is required.
            .PARAMETER DestinationPath
            The destination path of the file or directory. The path should be the absolute DBFS path (e.g. "/mnt/bar/"). This field is required.
            .EXAMPLE
            Move-DatabricksFSItem -SourcePath "/myFile.csv" -DestinationPath "/myFiles/myCSV.csv"
            .EXAMPLE
            #AUTOMATED_TEST:Move single file
            $sourcePath = "/myTestFolder/myFile1.txt"
            $targetPath = "/myTestFolder/myMovedFile.txt"
            $newFile = Add-DatabricksFSFile -Path $sourcePath -Overwrite $true
            Close-DatabricksFSFile -Handle $newFile.handle
            Remove-DatabricksFSItem -Path $targetPath -ErrorAction SilentlyContinue
            Move-DatabricksFSItem -SourcePath $sourcePath -DestinationPath $targetPath
    #>

    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)] [string] $SourcePath, 
        [Parameter(Mandatory = $true, Position = 2)] [string] $DestinationPath
    )
    
    $requestMethod = "POST"
    $apiEndpoint = "/2.0/dbfs/move"

    Write-Verbose "Building Body/Parameters for final API call ..."
    #Set parameters
    $parameters = @{
        source_path = $SourcePath 
        destination_path = $DestinationPath 
    }
    
    $result = Invoke-ApiRequest -Method $requestMethod -EndPoint $apiEndpoint -Body $parameters

    return $result
}

Function Get-FSContent
{
    <#
            .SYNOPSIS
            Returns the contents of a file. If the file does not exist, this call will throw an exception with RESOURCE_DOES_NOT_EXIST. If the path is a directory, the read length is negative, or if the offset is negative, this call will throw an exception with INVALID_PARAMETER_VALUE. If the read length exceeds 1 MB, this call will throw an exception with MAX_READ_SIZE_EXCEEDED. If offset + length exceeds the number of bytes in a file, we will read contents until the end of file.
            .DESCRIPTION
            Returns the contents of a file. If the file does not exist, this call will throw an exception with RESOURCE_DOES_NOT_EXIST. If the path is a directory, the read length is negative, or if the offset is negative, this call will throw an exception with INVALID_PARAMETER_VALUE. If the read length exceeds 1 MB, this call will throw an exception with MAX_READ_SIZE_EXCEEDED. If offset + length exceeds the number of bytes in a file, we will read contents until the end of file.
            Official API Documentation: https://docs.databricks.com/api/latest/dbfs.html#read
            .PARAMETER Path
            The path of the file to read. The path should be the absolute DBFS path (e.g. "/mnt/foo/"). This field is required.
            .PARAMETER Offset
            The offset to read from in bytes.
            .PARAMETER Length
            The number of bytes to read starting from the offset. This has a limit of 1 MB, and a default value of 0.5 MB.
            .PARAMETER Decode
            Adds a new property to the result that contains the decoded string value.
            .EXAMPLE
            Get-DatabricksFSContent -Path "/myFile.csv"
            .EXAMPLE
            #AUTOMATED_TEST:Get file content
            $content = "This is my test content!"
            $filePath = "/myTestFolder/myFile1.txt"
            $newFile = Add-DatabricksFSFile -Path $filePath -Overwrite $true
            Add-DatabricksFSFileBlock -Handle $newFile.handle -Data $content -AsPlainText
            Close-DatabricksFSFile -Handle $newFile.handle
            $readContent = Get-DatabricksFSContent -Path $filePath -Decode
            if($readContent.data_decoded -ne $content) { throw "Read content does not match written content!" }
    #>

    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)] [string] $Path, 
        [Parameter(Mandatory = $false, Position = 2)] [int] $Offset = -1, 
        [Parameter(Mandatory = $false, Position = 3)] [int] $Length = -1,
        [Parameter(Mandatory = $false, Position = 4)] [switch] $Decode
    )
    
    $requestMethod = "GET"
    $apiEndpoint = "/2.0/dbfs/read"

    Write-Verbose "Building Body/Parameters for final API call ..."
    #Set parameters
    $parameters = @{
        path = $Path 
    }

    $parameters | Add-Property -Name "offset" -Value $Offset -NullValue -1
    $parameters | Add-Property -Name "length" -Value $Length -NullValue -1
    
    $result = Invoke-ApiRequest -Method $requestMethod -EndPoint $apiEndpoint -Body $parameters

    if($Decode)
    {
        Write-Verbose "Decoding data ..."
        $decodedValue = $result.data | ConvertFrom-Base64 -Encoding ([Text.Encoding]::UTF8)
        Write-Verbose "Adding decoded data to result ..."
        Add-Member -InputObject $result -MemberType NoteProperty -Name "data_decoded" -Value $decodedValue
    }
    
    return $result
}




Function Upload-FSFile
{
    <#
            .SYNOPSIS
            Uploads a local file to the Databricks File System (DBFS)
            .DESCRIPTION
            Uploads a local file to the Databricks File System (DBFS).
            This cmdlet is basically a combination of Add-DatabricksFSFile, Add-DatabricksFSFileContent and Close-DatabricksFSFile.
            .PARAMETER Path
            The path of the new file to be created in DBFS. The path should be the absolute DBFS path (e.g. "/mnt/foo.txt"). This field is required.
            .PARAMETER LocalPath
            The path of the local file to be uploaded.
            .PARAMETER Overwrite
            The flag that specifies whether to overwrite existing file/files.
            .PARAMETER BatchSize
            The BatchSize to use when uploading the data
            .EXAMPLE
            Upload-DatabricksFSFile -Path '/DatabricksPS_Tests/test1.txt' -LocalPath ".\test1.txt" -Overwrite $true -Verbose -BatchSize 1000
    #>

    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)] [string] $Path, 
        [Parameter(Mandatory = $true, Position = 2)] [string] $LocalPath,
        [Parameter(Mandatory = $false, Position = 3)] [bool] $Overwrite = $false,
        [Parameter(Mandatory = $false, Position = 4)] [int] $BatchSize = 1048576
    )
    
    Write-Verbose "Creating new file in DBFS at $Path ..."
    $dbfsFile = Add-DatabricksFSFile -Path $Path -Overwrite $Overwrite
    
    Write-Verbose "Reading content from $LocalPath ..."
    $localFile = [System.IO.File]::ReadAllBytes($LocalPath)
    $totalSize = $localFile.Length
    
    Write-Verbose "Starting upload of file in batches of size $BatchSize ..."
    $offset = 0
    do
    {
        Write-Verbose "Adding new content from offset $offset ..."
        if($offset + $BatchSize -gt $totalSize)
        {
            $BatchSize = $totalSize - $offset
        }
        $content = $localFile[$offset..$($offset + $BatchSize)]
        $contentB64 = [System.Convert]::ToBase64String($content)
        
        Add-DatabricksFSFileBlock -Handle $dbfsFile.handle -Data $contentB64
        
        $offset = $offset + $BatchSize + 1
    }
    while($offset -lt $totalSize)
    Write-Verbose "Finished uploading local file '$LocalPath' to DBFS '$Path'"
    
    Close-DatabricksFSFile -Handle $dbfsFile.handle
    
    return $Path
}



Function Download-FSFile
{
    <#
            .SYNOPSIS
            Downloads a file from the Databricks File System (DBFS) to the local file system.
            .DESCRIPTION
            Downloads a file from the Databricks File System (DBFS) to the local file system.
            This cmdlet subsequently calls Get-DatabricksFSContent until the whole file is downloaded
            .PARAMETER Path
            The path of the file in DBFS that should be downloaded. The path should be the absolute DBFS path (e.g. "/mnt/foo.txt"). This field is required.
            .PARAMETER LocalPath
            The path where the downloaded file is stored locally.
            .PARAMETER Overwrite
            The flag that specifies whether to overwrite existing file/files.
            .PARAMETER BatchSize
            The BatchSize to use when uploading the data
            .EXAMPLE
            Download-DatabricksFSFile -Path '/DatabricksPS_Tests/test1.txt' -LocalPath ".\test1.txt" -Overwrite $true -Verbose -BatchSize 1000
    #>

    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true, Position = 1)] [string] $Path, 
        [Parameter(Mandatory = $true, Position = 2)] [string] $LocalPath,
        [Parameter(Mandatory = $false, Position = 3)] [bool] $Overwrite = $false,
        [Parameter(Mandatory = $false, Position = 4)] [int] $BatchSize = 1048576
    )
    
    $dbfsFile = Get-DatabricksFSItem -Path $Path
    
    if($dbfsFile.is_dir)
    {
        Write-Error "The specified path is a directory and not a file!"
    }
    
    if((Test-Path $LocalPath) -and $Overwrite)
    {
        Remove-Item $LocalPath -Force
    }
    
    $totalSize = $dbfsFile.file_size # number of bytes of the original file!
    
    Write-Verbose "Starting download of file in batches of size $BatchSize ..."
    Set-Content -Path $LocalPath -Value @() -Encoding Byte 
    $offset = 0
    do
    {
        Write-Verbose "Downloading new content from offset $offset ..."
        $dbfsFileContent = Get-DatabricksFSContent -Path $dbfsFile.path -Offset $offset -Length $BatchSize
        $dbfsByteContent = [System.Convert]::FromBase64String($dbfsFilecontent.data)
        
        Add-Content -Path $LocalPath -Value $dbfsByteContent -Encoding Byte -ErrorAction Stop
        
        $offset = $offset + $BatchSize
    } while ($offset -lt $totalSize)    
    Write-Verbose "Finished downloading DBFS file '$Path' to local file '$LocalPath'"
        
    return $LocalPath
}