Following my recent post Call Azure REST API from SQL Server I've received many requests about how to call the new API Copy Blob. Its description is here: As before, let me show you what we want to achieve:


We want to have a simple stored procedure to call with a storage account, a source and a destination blob (we need the shared key too).

EXEC Azure.CopyBlob '<storage>',
	'<source container>', '<source blob>',
	'<destination container>', '<destination blob>',

What we should do is to call the correct REST entity we want to create using the PUT verb. We must specify as header the source blob: x-ms-copy-source:name (don't forget to include it in the signature otherwise you'll get a very unpolite 403 forbidden!). Here is the main procedure code:

private static Dictionary<string, string> _CopyPageBlob(string storageAccount,
            string srcCcontainer, string srcBlobName,
            string destContainer, string destBlobName,
            string sharedKey)
            string strCopySource = string.Format("https://{0:S}{1:S}/{2:S}", storageAccount, srcCcontainer, srcBlobName);
            string strCopyDestination = string.Format("https://{0:S}{1:S}/{2:S}", storageAccount, destContainer, destBlobName);

            string CanonicalizedResource = "/";
            CanonicalizedResource += storageAccount;
            CanonicalizedResource += "/" + destContainer + "/" + destBlobName;

            System.Net.HttpWebRequest Request = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(strCopyDestination);
            Request.Method = "PUT";
            Request.ContentLength = 0;

#region Add HTTP headers
            string strDate = DateTime.UtcNow.ToString("R");

            Request.Headers.Add("x-ms-copy-source", strCopySource);
            Request.Headers.Add("x-ms-date", strDate);
            Request.Headers.Add("x-ms-version", "2012-02-12");

            string CanonicalizedHeader = "x-ms-copy-source:" + strCopySource + "\n";
            CanonicalizedHeader += "x-ms-date:" + strDate + "\n";
            CanonicalizedHeader += "x-ms-version:2012-02-12\n";

            string MessageSignature = Request.Method + "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";
            MessageSignature += Request.ContentLength + "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";

            MessageSignature += CanonicalizedHeader;
            MessageSignature += CanonicalizedResource;

            byte[] SignatureBytes = System.Text.Encoding.UTF8.GetBytes(MessageSignature);
            System.Security.Cryptography.HMACSHA256 SHA256 = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(sharedKey));

            String AuthorizationHeader = "SharedKey " + storageAccount + ":" + Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes));
            Request.Headers.Add("Authorization", AuthorizationHeader);

            using (System.Net.HttpWebResponse response = (System.Net.HttpWebResponse)Request.GetResponse())
                if (response.StatusCode == System.Net.HttpStatusCode.Accepted)
                    Dictionary<string, string> dRes = new Dictionary<string, string>();
                    for (int i = 0; i < response.Headers.Count; i++)
                        dRes.Add(response.Headers.GetKey(i), response.Headers.Get(i));
                    return dRes;
                    throw new Exception(response.ToString());

The wrapper SQL method is very simple then:

        public static void CopyPageBlob(SqlString storageAccount,
            SqlString sourceCcontainer, SqlString sourceBlobName,
            SqlString destinationContainer, SqlString destinationBlobName,
            SqlString sharedKey)
            Dictionary<string, string> dRes = _CopyPageBlob(
                sourceCcontainer.ToString(), sourceBlobName.ToString(),
                destinationContainer.ToString(), destinationBlobName.ToString(),


That alone should enable you to use this fantastic Azure feature. One point is to note, though: you might get the result before the copy is finished. The x-ms-copy-status header will tell you that (either success of pending); that's why we push it back to the caller in a recordset.


Happy coding,

Francesco Cogno