Powershell Invoke-WebRequest against Azure Storage Tables

To perform rest requests to azure storage tables you must sign your request, here is sample piece of code demonstrating how can it be done in Powershell:

$accountName = 'contoso'
$accountKey = '**************************************************************************************=='
$tableName = 'mytable'

$uri = "http://$accountName.table.core.windows.net/$tableName(PartitionKey='tasksSeattle',RowKey='1')"

$date = [datetime]::UtcNow.ToString('R', [System.Globalization.CultureInfo]::InvariantCulture)

$resource = [uri] $uri | select -ExpandProperty AbsolutePath # Path without query string

$stringToSign = $date + "`n/" + $accountName + $resource

$hasher = New-Object System.Security.Cryptography.HMACSHA256
$hasher.Key = [Convert]::FromBase64String($accountKey)
$signedSignature = [Convert]::ToBase64String($hasher.ComputeHash([Text.Encoding]::UTF8.GetBytes($stringToSign)))
$authorizationHeader = "SharedKeyLite " + $accountName + ":" + $signedSignature

$headers = @{
	Authorization = $authorizationHeader
	Date = $date
}

Write-Host 'Perform first request' -ForegroundColor Cyan
$response = Invoke-WebRequest -Uri $uri -Headers $headers
$response | select StatusCode, StatusDescription, @{n='ETag';e={ $_.Headers.ETag }}
$xml = [xml]$response.Content
$xml.entry.content.properties | fl

That code will return something like:

PartitionKey : tasksSeattle
RowKey       : 1
Timestamp    : Timestamp
Description  : Take out the trash.
DueDate      : DueDate
Location     : Home

Make sure to provide correct values, otherwise you will likely get Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signa ture. error.

Azure tables and conditional headers

It seems that Azure tables do not support conditional headers like If-Modified-Since like blobs do, but there is still one trick.

If you will make Invoke-WebRequest instead of Invoke-RestMethod you will also get response headers. And there will be ETag header - which is entity timestamp.

$response = Invoke-WebRequest -Uri $uri -Headers $headers
$response | select StatusCode, StatusDescription, @{n='ETag';e={ $_.Headers.ETag }} | fl
$xml = [xml]$response.Content
$xml.entry.content.properties | fl

Will return:

StatusCode        : 200
StatusDescription : OK
ETag              : W/"datetime'2015-12-11T10%3A53%3A52.6115577Z'"

PartitionKey : tasksSeattle
RowKey       : 1
Timestamp    : Timestamp
Description  : Take out the trash.
DueDate      : DueDate
Location     : Home

And now you can perform requests like:

$headers = @{
	Authorization = $authorizationHeader
	Date = $date
	'If-None-Match' = $response.Headers.ETag
}
Invoke-RestMethod -Uri $uri -Headers $headers

Which will give you desired (304) Not Modified.

Bootstraping example table from php

composer.json

{
  "repositories": [
    {
      "type": "pear",
      "url": "http://pear.php.net"
    }
  ],
  "require": {
    "pear-pear.php.net/mail_mime": "*",
    "pear-pear.php.net/http_request2": "*",
    "pear-pear.php.net/mail_mimedecode": "*",
    "microsoft/windowsazure": "*"
  }
}

sandbox.php

<?php
require_once 'vendor\autoload.php';
use WindowsAzure\Common\ServiceException;
use WindowsAzure\Common\ServicesBuilder;
use WindowsAzure\Table\Internal\ITable;
use WindowsAzure\Table\Models\EdmType;
use WindowsAzure\Table\Models\Entity;

$accountName = "contoso";
$accountKey = "**************************************************************************************==";
$connectionString = "DefaultEndpointsProtocol=https;AccountName=$accountName;AccountKey=$accountKey";

/** @var ITable $tableRestProxy */
$tableRestProxy = ServicesBuilder::getInstance()->createTableService($connectionString);

try {
	$createTableResult = $tableRestProxy->createTable("mytable");
} catch(ServiceException $ex) {
	if($ex->getCode() !== 409) throw $ex;
}

$entity = new Entity();
$entity->setPartitionKey("tasksSeattle");
$entity->setRowKey("1");
$entity->addProperty("Description", null, "Take out the trash.");
$entity->addProperty("DueDate", EdmType::DATETIME, new DateTime("2012-11-05T08:15:00-08:00"));
$entity->addProperty("Location", EdmType::STRING, "Home");

try {
	$tableRestProxy->insertEntity("mytable", $entity);
} catch(ServiceException $ex) {
	if($ex->getCode() !== 409) throw $ex;
}

Some links

ETag

Specifying Conditional Headers for Blob Service Operations

Authenticating against Azure Table Storage

Authentication for the Azure Storage Services

How to use table storage from PHP