AzurePSDriveStorageAccount.psm1

using namespace Microsoft.PowerShell.SHiPS


[SHiPSProvider(UseCache=$true)]
class StorageAccounts : SHiPSDirectory
{
    StorageAccounts() : base ($this.GetType())
    {
    }

    StorageAccounts([string]$name): base($name)
    {
    }

    [object[]] GetChildItem()
    {
        $obj =  @()                     
        @(Az.Storage\Get-AzStorageAccount).Foreach{
             $obj += [StorageAccount]::new($($_.StorageAccountName), $_) 
        }          
        return $obj 
    }
 }

[SHiPSProvider(UseCache=$true)]
class StorageAccount : SHiPSDirectory
{ 
    Hidden [object]$data = $null

    StorageAccount ([string]$name, [object]$data) : base ($name) 
    {
        $this.data = $data
    }

    [object[]] GetChildItem()
    {        
        $obj =  @()
        $ev = $null
        if ($this.data.PrimaryEndpoints.Blob -ne $null)
        {
            try
            {
                $result=Az.Storage\Get-AzStorageContainer -Context $this.data.Context -ErrorAction SilentlyContinue -ErrorVariable ev
                if($ev) {
                    Write-Verbose $ev.Exception
                }else{
                    $obj+=[Blobs]::new($this.data, $result);
                }
            }
            catch
            {
                Write-Verbose $_.Exception
            }
        }

        if ($this.data.PrimaryEndpoints.File -ne $null)
        {
            try
            {
                $result=Az.Storage\Get-AzStorageShare -Context $this.data.Context -ErrorAction SilentlyContinue -ErrorVariable ev
                if ($ev) {
                    Write-Verbose $ev.Exception
                } else {
                    $obj+=[Files]::new($this.data, $result)
                }
            }
            catch
            {
                Write-Verbose $_.Exception
            }
        }

        if ($this.data.PrimaryEndpoints.Table -ne $null)
        {
            try
            {
                $result=Az.Storage\Get-AzStorageTable -Context $this.data.Context  -ErrorAction SilentlyContinue -ErrorVariable ev
                if ($ev){
                    Write-Verbose $ev.Exception 
                } else {
                    $obj+=[Tables]::new($this.data, $result);
                }
            }
            catch
            {
                Write-Verbose $_.Exception
            }
        }

        if ($this.data.PrimaryEndpoints.Queue -ne $null)
        {
            try {
                $result=Az.Storage\Get-AzStorageQueue -Context $this.data.Context -ErrorAction SilentlyContinue -ErrorVariable ev
                if ($ev) {
                    Write-Verbose $ev.Exception
                } else {
                    $obj+=[Queues]::new($this.data, $result);
                }
            }
            catch
            {
                Write-Verbose $_.Exception
            }
        }

        return $obj;
    }
}

[SHiPSProvider(UseCache=$true)]
class Blobs : SHiPSDirectory
{
    Hidden [object]$data = $null
    Hidden [object]$result = $null

    Blobs ([object]$data, [object]$result) : base ($this.GetType()) 
    {
        $this.data = $data
        $this.result = $result
    }

    [object[]] GetChildItem()
    {     
        if(-not $this.result)
        {
            Write-Debug "AzurePSDrive: No Blobs."
            return $null
        }

        $obj =  @()
        $this.result | ForEach-Object{     
            $obj+= [Blob]::new($_.Name, $_);                  
        }         
        return $obj     
    }

    [object] SetContent([string]$content, [string]$path)
    {
        Write-Error -Message "Set-Content is not supported under 'Blobs'. Try again under Files\<Share> directory."
        return $null
    }
}

[SHiPSProvider(UseCache=$true)]
class Files : SHiPSDirectory
{
    Hidden [object]$data = $null
    Hidden [object]$result = $null

    Files ([object]$data, [object]$result) : base ($this.GetType()) 
    {
        $this.data = $data
        $this.result = $result        
    }

    [object[]] GetChildItem()
    {      
        if(-not $this.result)
        {
            Write-Debug "AzurePSDrive: No Files found."
            return $null
        }

        <# ConnectionString from cmdlet returns something like below:
            BlobEndpoint=https://<accountname>.blob.core.windows.net/;QueueEndpoint=https://<accountname>.queue.core.windows.net/;
            TableEndpoint=https://<accountname>.table.core.windows.net/;FileEndpoint=https://<accountname>.file.core.windows.net/;AccountName=<accountname>;AccountKey=<token>
        #>

        # Parse the orginal connection string, extract FileEndpoint, and make it net use-able.
        $conectstring = $this.data.Context.ConnectionString -split ';'
        $accountInfo=$conectstring | Where-Object { $_ -match 'AccountName' -or $_ -match 'AccountKey'}
        $fileEndPoint = $conectstring  | Where-Object {$_ -match 'FileEndpoint'}
        $fileshare = $fileEndPoint

        if($fileEndPoint) {
            $index = $fileEndPoint.IndexOf('//')
            if ($index -gt 0) {
                $temp = $fileEndPoint.Substring($index)
                if($temp) {
                    $fileshare = $temp.Replace('/', '\') 
                }   
            }
        }

        return  $this.result | ForEach-Object {           
             [FileShare]::new($_.Name, $this.data.Context, "$fileshare$($_.Name);$($accountInfo)");
            }
    }

    [object] SetContent([string]$content, [string]$path)
    {
        Write-Error -Message "Set-Content is not supported under 'Files'. Try again under its sub-directories."
        return $null
    }
}

[SHiPSProvider(UseCache=$true)]
class Tables : SHiPSDirectory
{
    Hidden [object]$data = $null
    Hidden [object]$result = $null

    Tables ([object]$data, [object]$result) : base ($this.GetType()) 
    {
        $this.data = $data
        $this.result = $result        
    }

    [object[]] GetChildItem()
    {     
        return @($this.result | Sort-Object Name)    
    }

    [object] SetContent([string]$content, [string]$path)
    {
        Write-Error -Message "Set-Content is not supported under 'Tables'. Try again under Files\<Share> directory."
        return $null
    }
}

[SHiPSProvider(UseCache=$true)]
class Queues : SHiPSDirectory
{
    Hidden [object]$data = $null
    Hidden [object]$result = $null

    Queues ([object]$data, [object]$result) : base ($this.GetType()) 
    {
        $this.data = $data
        $this.result = $result                
    }

    [object[]] GetChildItem()
    {     
        return @($this.result | Sort-Object Name)  
    }

    [object] SetContent([string]$content, [string]$path)
    {
        Write-Error -Message "Set-Content is not supported under 'Queues'. Try again under Files\<Share> directory."
        return $null
    }
}

[SHiPSProvider(UseCache=$true)]
class FileShare : SHiPSDirectory
{ 
    Hidden [string]$shareName = $null
    [object]$Context = $null
    [string]$ConnectionString = $null

    FileShare ([string]$name, [object]$context, [string]$connectionString) : base ($name) 
    {
        $this.shareName = $name        
        $this.context = $context
        $this.ConnectionString = $connectionString
    }

    [object[]] GetChildItem()
    {
        $obj =  @()

        Az.Storage\Get-AzStorageFile -Context $this.context -ShareName $this.shareName | ForEach-Object {
            if($_.GetType().Name -eq "CloudFileDirectory") {
                $obj+=[FileFolder]::new($_.Name, $this.shareName, $_.Name, $this.context, $_)
            } else {
                $obj+=[FileShareLeaf]::new($_.Name, $this.shareName, $null, $this.context)
            }
        }

        return $obj
    }

    [object] SetContent([string]$content, [string]$path)
    {
        $leafName = SetContentUtility -Content $content -Path $path -ShareName $this.shareName -FolderName $null -Context $this.context
        if($leafName)
        {
            # Returning the currently object so that the SHiPS can cache it
            return [FileShareLeaf]::new($leafName, $this.shareName, $null, $this.context)
        }
        return $null
    }
}

class FileShareLeaf : SHiPSLeaf
{
    Hidden [string]$fileName = $null
    Hidden [string]$shareName = $null
    Hidden [object]$context = $null
    Hidden [string]$folderName = $null
    Hidden [string]$filePath = $null

    FileShareLeaf([string]$name, [string]$shareName, [string]$folderName, [object]$context): base($name)
    {
        $this.context = $context
        $this.fileName = $name
        $this.shareName = $shareName
        $this.folderName = $folderName
        if($this.folderName)
        {
            $this.filePath = join-path $this.folderName $this.fileName
        }
        else
        {
            $this.filePath = $this.fileName
        }
    }

    [object] GetContent()
    {
        $tmpfile = $null
        try {
            $tmpfile = [System.IO.Path]::GetTempFileName()

            Write-Verbose "Calling Get-AzStorageFilecontent -Path $($this.filePath) -ShareName $($this.shareName) -Destination $tmpfile ..." -Verbose

            $ev = $null
            Az.Storage\Get-AzStorageFilecontent -Path $this.filePath -Context $this.context -ShareName $this.shareName -Destination $tmpfile -Force -ErrorVariable ev

            if(-not $ev)
            {
                $bp = $this.ProviderContext.BoundParameters

                if($bp.ContainsKey('Path'))
                {
                    $null = $bp.Remove('Path')
                }

                return Microsoft.PowerShell.Management\Get-Content -Path $tmpfile @bp
            }
            return $null
        }
        finally {
            if(Test-Path $tmpfile) {
                Microsoft.PowerShell.Management\Remove-Item -Path $tmpfile -Force -ErrorAction SilentlyContinue
            }
        }
    }

    [object] SetContent([string]$content, [string]$path)
    {
        $leafName = SetContentUtility -Content $content -Path $path -ShareName $this.shareName -FolderName $this.folderName  -Context $this.context

        if($leafName)
        {
            # Returning the currently object so that the SHiPS can cache it
            return $this
        }

        return $null
    }
}

[SHiPSProvider(UseCache=$true)]
class FileFolder : SHiPSDirectory
{
    Hidden [string]$folderName = $null
    Hidden [object]$cloudFileDirectory = $null

    Hidden [object]$context = $null
    Hidden [string]$shareName = $null

    FileFolder ([string]$name, [string]$shareName, [string]$dir, [object]$context, [object]$cloudFileDirectory) : base ($name)
    {
        $this.folderName = $dir
        $this.cloudFileDirectory = $cloudFileDirectory
        $this.context = $context
        $this.shareName = $shareName
    }

    [object[]] GetChildItem()
    { 
        $obj =  @()

        Az.Storage\Get-AzStorageFile -Directory $this.cloudFileDirectory | ForEach-Object {
            if($_.GetType().Name -eq "CloudFileDirectory") {
                if($this.folderName)
                {
                    $dir = Microsoft.PowerShell.Management\Join-path -Path $this.folderName -ChildPath $_.Name
                }
                else {
                    $dir=$_.Name
                }
                $obj+=[FileFolder]::new($_.Name, $this.shareName, $dir, $this.context, $_)

            } else {
                $obj+=[FileShareLeaf]::new($_.Name, $this.shareName, $this.folderName, $this.context )
            }
        }
        return $obj
    }

    [object] SetContent([string]$content, [string]$path)
    {
        Write-Verbose "Folder = $($this.folderName), CurrentNode = $($this.name)"
        $leafName = SetContentUtility -Content $content -Path $path -ShareName $this.shareName -FolderName $this.folderName -Context $this.context
        # Returning the currently object so that the SHiPS can cache it
        if($leafName)
        {
            return [FileShareLeaf]::new($leafName, $this.shareName, $this.folderName, $this.context)
        }

        return $null
    }
}

[SHiPSProvider(UseCache=$true)]
class Blob : SHiPSDirectory
{ 
    Hidden [object]$data = $null

    Blob ([string]$name, [object]$data) : base ($name) 
    {
        $this.data = $data
    }

    [object[]] GetChildItem()
    {      
        return @(Az.Storage\Get-AzStorageBlob -Context $this.data.Context -Container $this.data.Name | Sort-Object Name)
    }
}


Function SetContentUtility()
{
    param(
        [string]$Content,
        [string]$Path,
        [string]$ShareName,
        [string]$FolderName,
        [object]$Context
    )

    # Save the text content to a local temp file because the Set-AzureStorageFileContent cmdlet takes file only
    $tmpfile = [System.IO.Path]::GetTempFileName()

    try {

        Microsoft.PowerShell.Management\Set-Content -Path $tmpfile -Value $Content

        # $content is the 'Value' passed in from Set-Content
        # $path is the full path. e.g., Azure:\<subscription>\StorageAccounts\<myaccount>\Files\<Share>\hello.ps1".

        # Get the file share target path for Set-AzStorageFileContent
        # strip off from the path until the share name
        $restPath = $Path -replace  "^.*$ShareName"
        if(-not $restPath)
        {
            Write-Error -Message "Set-Content is not supported on FileShare: $Path. Only files are supported."
            return $null
        }
        $destionation = $restPath.TrimStart('/\')
        if(-not $destionation)
        {
            Write-Error -Message "Set-Content is not supported on FileShare: $Path. Only files are supported."
            return $null
        }
        Write-Verbose "Folder = $FolderName, Path = $path, Destionation = $destionation"
        if($FolderName -eq $destionation)
        {
            Write-Error -Message "Set-Content is not supported on directory path: '$FolderName'. Only files are supported."
            return $null
        }

        # See details https://docs.microsoft.com/en-us/powershell/module/azure.storage/set-azurestoragefilecontent?view=azurermps-6.8.1
        Write-Verbose "Calling Set-AzStorageFileContent -ShareName $ShareName -Source $tmpfile -Path $destionation" -Verbose
        $ev = $null
        Az.Storage\Set-AzStorageFileContent -Context $Context -ShareName $ShareName -Source $tmpfile -Path $destionation -Force -ErrorVariable ev

        if($ev) { return $null }

        return (Microsoft.PowerShell.Management\Split-Path $Path -Leaf)
    }
    finally {
        if($tmpfile) {
            Microsoft.PowerShell.Management\Remove-Item $tmpfile -Force -ErrorAction SilentlyContinue
        }
    }
}