Export-Blob.ps1

function Export-Blob
{
    <#
    .Synopsis
        Exports data to a cloud blob
    .Description
        Exports data to a blob in Azure
    .Example
        Get-ChildItem -Filter *.ps1 | Export-Blob -Container scripts -StorageAccount astorageAccount -StorageKey (Get-SecureSetting aStorageKey -ValueOnly)
    .Link
        Get-Blob
    .Link
        Import-Blob
    .Link
        Remove-Blob
    #>

    [OutputType([Nullable])]    
    param(
    # The input object for the blob.
    [Parameter(ValueFromPipeline=$true)]
    [PSObject]
    $InputObject,

    # The name of the container
    [Parameter(Mandatory=$true,Position=0, ValueFromPipelineByPropertyName=$true)]
    [string]$Container,

    # The name of the blob
    [Parameter(Mandatory=$true,Position=1,ValueFromPipelineByPropertyName=$true)]
    [string]$Name,

    # The content type. If a file is provided as input, this will be provided automatically. If not, it will be text/plain
    [string]$ContentType = "text/plain",

    # The storage account
    [string]$StorageAccount,

    # The storage key
    [string]$StorageKey,

    # If set, the container the blob is put into will be made public
    [Switch]
    $Public
    )


    begin {

        #region Create a lookup table of mime types
        if (-not $script:cachedContentTypes) {
            $script:cachedContentTypes = @{}
            $ctKey = [Microsoft.Win32.Registry]::ClassesRoot.OpenSubKey("MIME\Database\Content Type")
            $ctKey.GetSubKeyNames() |
                ForEach-Object {
                    $extension= $ctKey.OpenSubKey($_).GetValue("Extension") 
                    if ($extension) {
                        $script:cachedContentTypes["${extension}"] = $_
                    }
                }

        }
        #endregion Create a lookup table of mime types

        # Create a lambda to sign messages
$signMessage = {
    param(
    [Hashtable]$Header,
    [Uri]$Url,
    [Uint32]$ContentLength,
    [string]$IfMatch  ="",
    [string]$Md5OrContentType = "",
    [string]$NowString = [DateTime]::now.ToString("R", [Globalization.CultureInfo]::InvariantCulture),
    [Switch]$IsTableStorage,    
    [string]$method = "GET",
    [string]$Storageaccount,
    [string]$StorageKey
    )

    $method = $method.ToUpper()
    $MessageSignature = 
    if ($IsTableStorage) {
        [String]::Format("{0}`n`n{1}`n{2}`n{3}",@(
            $method,
            "application/atom+xml",
            $NowString,
            ( & $GetCanonicalizedResource $Url $StorageAccount)))

    } else {
        if ($md5OrCOntentType) {
            [String]::Format("{0}`n`n`n{1}`n`n{5}`n`n`n{2}`n`n`n`n{3}{4}", @(
                $method,
                $(if ($method -eq "GET" -or $method -eq "HEAD") {[String]::Empty} else { $ContentLength }),
                $IfMatch,
                "$(& $GetCanonicalizedHeader $Header)",
                "$( & $GetCanonicalizedResource $Url $StorageAccount)",
                $Md5OrContentType
                ));
        } else {
            [String]::Format("{0}`n`n`n{1}`n{5}`n`n`n`n{2}`n`n`n`n{3}{4}", @(
                $method,
                $(if ($method -eq "GET" -or $method -eq "HEAD") {[String]::Empty} else { $ContentLength }),
                $IfMatch,
                "$(& $GetCanonicalizedHeader $Header)",
                "$( & $GetCanonicalizedResource $Url $StorageAccount)",
                $Md5OrContentType
                ));
        }
        
    }    

    $SignatureBytes = [Text.Encoding]::UTF8.GetBytes($MessageSignature)

    [byte[]]$b64Arr = [Convert]::FromBase64String($StorageKey)
    $SHA256 = new-object Security.Cryptography.HMACSHA256 
    $sha256.Key = $b64Arr
    $AuthorizationHeader = "SharedKey " + $StorageAccount + ":" + [Convert]::ToBase64String($SHA256.ComputeHash($SignatureBytes))
    $AuthorizationHeader 
}

$GetCanonicalizedHeader = {
    param(
    [Hashtable]$Header
    )

    $headerNameList = new-OBject  Collections.ArrayList;
    $sb = new-object Text.StringBuilder;
    foreach ($headerName in $Header.Keys) {
        if ($headerName.ToLowerInvariant().StartsWith("x-ms-", [StringComparison]::Ordinal)) {
                $null = $headerNameList.Add($headerName.ToLowerInvariant());
        }
    }
    $null = $headerNameList.Sort();
    [Collections.Specialized.NameValueCollection]$headers =NEw-OBject Collections.Specialized.NameValueCollection    
    foreach ($h in $header.Keys) {
        $null = $headers.Add($h, $header[$h])
    }

    
    foreach ($headerName in $headerNameList)
    {
        $builder = new-Object Text.StringBuilder $headerName
        $separator = ":";
        foreach ($headerValue in (& $GetHeaderValues $headers $headerName))
        {
            $trimmedValue = $headerValue.Replace("`r`n", [String]::Empty)
            $null =  $builder.Append($separator)
            $null = $builder.Append($trimmedValue)
            $separator = ","
        }
        $null = $sb.Append($builder.ToString())
        $null = $sb.Append("`n")
    }
    return $sb.ToString()    
}


$GetHeaderValues  = {
    param([Collections.Specialized.NameValueCollection]$headers, $headerName)
    $list = new-OBject  Collections.ArrayList
    
    $values = $headers.GetValues($headerName)
    if ($values -ne $null)
    {
        foreach ($str in $values) {
            $null = $list.Add($str.TrimStart($null))
        }
    }
    return $list;
}

$GetCanonicalizedResource = {
    param([uri]$address, [string]$accountName)

    $str = New-object Text.StringBuilder
    $builder = New-object Text.StringBuilder "/" 
    $null = $builder.Append($accountName)
    $null = $builder.Append($address.AbsolutePath)
    $null = $str.Append($builder.ToString())
    $values2 = New-Object Collections.Specialized.NameValueCollection
    if (!$IsTableStorage) {
        $values = [Web.HttpUtility]::ParseQueryString($address.Query)
        foreach ($str2 in $values.Keys) {
            $list = New-Object Collections.ArrayList 
            foreach ($v in $values.GetValues($str2)) {
                $null = $list.add($v)
            }
            $null = $list.Sort();
            $builder2 = New-Object Text.StringBuilder
            foreach ($obj2 in $list)
            {
                if ($builder2.Length -gt 0)
                {
                    $null = $builder2.Append(",");
                }
                $null = $builder2.Append($obj2.ToString());
            }
            $valueName = if ($str2 -eq $null) {
                $str2 
            } else {
                $str2.ToLowerInvariant()
            }
            $values2.Add($valueName , $builder2.ToString())
        }
    }
    $list2 = New-Object Collections.ArrayList 
    foreach ($k in $values2.AllKeys) {
        $null = $list2.Add($k)
    }
    $null = $list2.Sort()
    foreach ($str3 in $list2)
    {
        $builder3 = New-Object Text.StringBuilder([string]::Empty);
        $null = $builder3.Append($str3);
        $null = $builder3.Append(":");
        $null = $builder3.Append($values2[$str3]);
        $null = $str.Append("`n");
        $null = $str.Append($builder3.ToString());
    }
    return $str.ToString();

}

        #$inputList = New-Object Collections.ArrayList
        $inputData = New-Object Collections.ArrayList

        if (-not $script:alreadyPublicContainers) {
            $script:alreadyPublicContainers = @{}
        }

        if (-not $script:knownContainers) {
            $script:knownContainers= @{}
        }
    }


    process {
        #$null = $inputList.Add($inputObject)
        $null = $inputData.Add((@{} + $psBoundParameters)) 
    }

    end {
        #region check for and cache the storage account
        if (-not $StorageAccount) {
            $storageAccount = $script:CachedStorageAccount
        }

        if (-not $StorageKey) {
            $StorageKey = $script:CachedStorageKey
        }

        if (-not $StorageAccount) {
            Write-Error "No storage account provided"
            return
        }

        if (-not $StorageKey) {
            Write-Error "No storage key provided"
            return
        }

        $script:CachedStorageAccount = $StorageAccount
        $script:CachedStorageKey = $StorageKey
        #endregion check for and cache the storage account
        foreach ($inputInfo in $inputData) {
            if ($inputInfo.Name) {
                $Name = $inputInfo.Name
            }

            if ($inputInfo.Container) {
                $Container = $inputInfo.Container
            }

            $InputObject = $inputInfo.InputObject
        
            $containerBlobList = $null
            $Container = "$Container".ToLower()
            
            if (-not $knownContainers[$Container]) {
                $method = 'GET'
                $uri = "http://$StorageAccount.blob.core.windows.net/${Container}?restype=container&comp=list&include=metadata"
                $header = @{
                    "x-ms-date" = $nowString 
                    "x-ms-version" = "2011-08-18"
                    "DataServiceVersion" = "2.0;NetFx"
                    "MaxDataServiceVersion" = "2.0;NetFx"

                }
                $header."x-ms-date" = [DateTime]::Now.ToUniversalTime().ToString("R", [Globalization.CultureInfo]::InvariantCulture)
                $nowString = $header.'x-ms-date'
                $header.authorization = . $signMessage -header $Header -url $Uri -nowstring $nowString -storageaccount $StorageAccount -storagekey $StorageKey -contentLength 0 -method GET
        
            
                $containerBlobList = Get-Web -UseWebRequest -Header $header -Url $Uri -Method GET -ErrorAction SilentlyContinue -ErrorVariable err -HideProgress

                if ($containerBlobList) {
                    $knownContainers[$Container] = $knownContainers[$Container]
                }
            }
        
            if (-not $containerBlobList) {
                # Tries to create the container if it's not found
                $method = 'PUT'
                $uri = "http://$StorageAccount.blob.core.windows.net/${Container}?restype=container"

                $header = @{
                    "x-ms-date" = $nowString 
                    "x-ms-version" = "2011-08-18"
                    "DataServiceVersion" = "2.0;NetFx"
                    "MaxDataServiceVersion" = "2.0;NetFx"

                }
                $header."x-ms-date" = [DateTime]::Now.ToUniversalTime().ToString("R", [Globalization.CultureInfo]::InvariantCulture)
                $nowString = $header.'x-ms-date'
                $header.authorization = . $signMessage -header $Header -url $Uri -nowstring $nowString -storageaccount $StorageAccount -storagekey $StorageKey -contentLength 0 -method PUT
                $Putresult = 
                    try {
                        Get-Web -UseWebRequest -Header $header -Url $Uri -Method PUT -HideProgress                 
                    } catch {
                        $_
                    }
                $null = $Putresult
            
            }
        

            if ($Public -and -not $script:alreadyPublicContainers[$Container]){
                
                # Enables public access to the container
                $acl =@"
<?xml version="1.0" encoding="utf-8"?>
  <SignedIdentifiers>
    <SignedIdentifier>
        <Id>Policy1</Id>
        <AccessPolicy>
        <Start>2011-01-01T09:38:05Z</Start>
        <Expiry>2011-12-31T09:38:05Z</Expiry>
        <Permission>r</Permission>
    </AccessPolicy>
   </SignedIdentifier>
   <SignedIdentifier>
     <Id>Policy2</Id>
       <AccessPolicy>
           <Start>2010-01-01T09:38:05Z</Start>
           <Expiry>2012-12-31T09:38:05Z</Expiry>
           <Permission>r</Permission>
       </AccessPolicy>
   </SignedIdentifier>
</SignedIdentifiers>
"@

        
                $aclBytes= [Text.Encoding]::UTF8.GetBytes("$acl")    
                $method = 'PUT'
                $uri = "http://$StorageAccount.blob.core.windows.net/${Container}?restype=container&comp=acl"
                $header = @{
                    "x-ms-date" = $nowString 
                    "x-ms-version" = "2011-08-18"
                    "x-ms-blob-public-access" = "container"
                    "DataServiceVersion" = "2.0;NetFx"
                    "MaxDataServiceVersion" = "2.0;NetFx"
                    'content-type' = $ct 
                }
                $header."x-ms-date" = [DateTime]::Now.ToUniversalTime().ToString("R", [Globalization.CultureInfo]::InvariantCulture)
                $nowString = $header.'x-ms-date'
                $ct ='application/x-www-form-urlencoded'
                $header.authorization = & $signMessage -header $Header -url $Uri -nowstring $nowString -storageaccount $StorageAccount -storagekey $StorageKey -contentLength $aclBytes.Length -method PUT  -md5OrContentType $ct 
                $Created = Get-Web -UseWebRequest -Header $header -Url $Uri -Method PUT -RequestBody $aclBytes -ErrorAction SilentlyContinue 
                $null = $Created
                $script:alreadyPublicContainers[$Container] = $Container

            
            }


            $uri = "http://$StorageAccount.blob.core.windows.net/$Container/$Name"


            # Turn our input into bytes
            if ($InputObject -is [IO.FileInfo]) {
                $bytes = [io.fIle]::ReadAllBytes($InputObject.Fullname)
                $extension = [IO.Path]::GetExtension($InputObject.Fullname)
                $mimeType = $script:CachedContentTypes[$extension]
                if (-not $mimeType) {
                    $mimetype = "unknown/unknown"
                }
            } elseif ($InputObject -as [byte[]]) {
                $bytes = $InputObject -as [byte[]]
            } else {
                $bytes = [Text.Encoding]::UTF8.GetBytes("$InputObject")
            }

            if (-not $mimetype -or $psBoundParameters.ContentType) {
                $mimeType = $ContentType
            }

            $method = 'PUT'
            $header = @{
                'x-ms-blob-type' = 'BlockBlob'
                "x-ms-date" = $nowString 
                "x-ms-version" = "2011-08-18"
                "DataServiceVersion" = "2.0;NetFx"
                "MaxDataServiceVersion" = "2.0;NetFx"
                'content-type' = $mimeType 
            }
            $header."x-ms-date" = [DateTime]::Now.ToUniversalTime().ToString("R", [Globalization.CultureInfo]::InvariantCulture)
            $nowString = $header.'x-ms-date'
            $header.authorization = . $signMessage -header $Header -url $Uri -nowstring $nowString -storageaccount $StorageAccount -storagekey $StorageKey -contentLength $bytes.Length -method PUT -md5OrContentType $mimeType
        
        
            $blobData= Get-Web -UseWebRequest -Header $header -Url $Uri -Method PUT -RequestBody $bytes -ContentType $mimeType 
            $null = $blobData
        }
    }
}