I've been struggling with what appears to be a common problem: formatting my authorization header for the Azure Table Service REST API. I have been unable to find an example using PowerShell and SharedKey, and am worried that I am making some dumb mistake in working backwards from other examples.
The specific (though unspecific) error is: Server failed to authenticate the request. Make sure the value of Authorization header is formed correctly including the signature.
I have been referencing these articles, as well as other examples:
- https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/Authentication-for-the-Azure-Storage-Services?redirectedfrom=MSDN
- https://docs.microsoft.com/en-us/rest/api/storageservices/fileservices/addressing-table-service-resources
I've confirmed that my key is correct, the table exists, has at least one row, and a number of other suggestions without luck.
If there is another approach I should take to accomplish the same goal, I'm all ears. I would like to use the REST API to allow for maximum compatibility on the clients.
$tableEndpoint = 'https://STORAGEACCOUNTNAME.table.core.windows.net/'
$tableName = 'TABLENAME'
$StorageAccountName = 'STORAGEACCOUNTNAME'
$Key = "STORAGEACCOUNTKEY"
Function New-AuthorizationHeader
{
param ($canonicalizedString)
[byte[]]$Bytes = [system.convert]::FromBase64String($Key)
$HMACSHA256 = [System.Security.Cryptography.HMACSHA256]::new($Bytes)
$dataToHmac = [System.Text.Encoding]::UTF8.GetBytes($canonicalizedString)
$Signature = [System.Convert]::ToBase64String($HMACSHA256.ComputeHash($dataToHmac))
[string]$AuthorizationHeader = "{0} {1}:{2}" -f "SharedKey",$StorageAccountName,$Signature
$AuthorizationHeader
}
Function New-Entity
{
param ($jsonContent)
$requestMethod = "POST"
$contentMD5 = [string]::Empty
$storageServiceVersion = '2016-05-31'
$reqDate = (Get-Date -Format r)
$contentType = "application/json"
$canonicalizedResource = "/{0}/{1}" -f $StorageAccountName,($tableEndpoint + $tableName)
$stringToSign = "{0}`n{1}`n{2}`n{3}`n{4}" -f $requestMethod,$contentMD5,$contentType,$reqDate,$canonicalizedResource
$authorizationHeader = New-AuthorizationHeader -canonicalizedString $stringToSign
$content = [System.Text.Encoding]::UTF8.GetBytes($jsonContent)
$fullURI = New-Object -TypeName System.Uri -ArgumentList ($tableEndpoint + $tableName)
$httpWebRequest = [System.Net.WebRequest]::Create($fullURI)
$httpWebRequest.Accept = 'application/json;odata=fullmetadata'
$httpWebRequest.ContentLength = $content.length
$httpWebRequest.ContentType = $contentType
$httpWebRequest.Method = $requestMethod
$httpWebRequest.Headers.Add("x-ms-date", $reqDate)
$httpWebRequest.Headers.Add("x-ms-version", $storageServiceVersion)
$httpWebRequest.Headers.Add("Authorization", $authorizationHeader)
$httpWebRequest.Headers.Add("Accept-Charset", "UTF-8")
$httpWebRequest.Headers.Add("DataServiceVersion", "3.0;NetFx")
$httpWebRequest.Headers.Add("MaxDataServiceVersion", "3.0;NetFx")
$requestStream = $httpWebRequest.GetRequestStream()
$requestStream.Write($content, 0, $content.length)
$requestStream.Close()
$response = $httpWebRequest.GetResponse()
$dataStream = $response.GetResponseStream()
$reader = New-Object -TypeName System.IO.StreamReader($dataStream)
$responseFromServer = $reader.ReadToEnd()
}
$jsonContent = @"
{
"ExecutionStatus"="smapledata",
"PartitionKey"="$ENV:Username",
"RowKey"="PrinterScript"
}
"@
New-Entity -jsonContent $jsonContent