How to create a function app in Azure Portal without VS

Here is the steps that create a function app in Azure Portal without VS.

  1. New function

  2. HTTP Trigger

  3. Enter a Name.

  4. View files -> Add -> function.proj file -> copy my sample into it -> Save

Please copy below xml doc into function.proj

1
2
3
4
5
6
7
8
9
10
11
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Azure.Management.ResourceManager.Fluent" Version="1.27.2" />
<PackageReference Include="Microsoft.Azure.Management.Websites" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="1.0.29" />
</ItemGroup>
</Project>
  1. Click “run.csx” file -> copy my code and paste it -> Save -> Test it.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#r "Newtonsoft.Json"

using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Management.ResourceManager.Fluent;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Rest;
using System.Linq;

public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");

int capacity;

if (!string.IsNullOrEmpty(req.Query["capacity"]) && int.TryParse(req.Query["capacity"], out capacity))
{
log.LogInformation("You want to chagne the capacity to "+ capacity.ToString());
// Step1. How to: Use the portal to create an Azure AD application and service principal that can access resources
// https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal

// Step2. How to: Add app roles in your application and receive them in the token
// https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps
// For security concerns, I only give the resource group => contributor
// and the service plan read permission

var subscriptionId = "Your Subscription Id";
var appId = "e6f6d231-Your-App-Id";
var secretKey = "2z]pj0vy2JRapjo:R@TJSoW1AmOCC=o8";
var tenantId = "72f988bf-Your Tenant-id";
var resourceGroup = "jackywebl1rg";
var servicePlanName = "ASP-jackywebl1rg-81ee";


// Step 3. change capacity
var context = new AuthenticationContext("https://login.windows.net/" + tenantId);
ClientCredential clientCredential = new ClientCredential(appId, secretKey);
var tokenResponse = context.AcquireTokenAsync("https://management.azure.com/", clientCredential).Result;
var accessToken = tokenResponse.AccessToken;
TokenCredentials credential = new TokenCredentials(accessToken);
var webSiteManagementClient = new Microsoft.Azure.Management.WebSites.WebSiteManagementClient(credential);
webSiteManagementClient.SubscriptionId = subscriptionId;
var servicePlan = webSiteManagementClient.AppServicePlans.ListByResourceGroupWithHttpMessagesAsync(resourceGroup).Result.Body.Where(x => x.Name.Equals(servicePlanName)).FirstOrDefault();

var appServicePlanRequest = await webSiteManagementClient.AppServicePlans.ListByResourceGroupWithHttpMessagesAsync(resourceGroup);
appServicePlanRequest.Body.ToList().ForEach(x => log.LogInformation($">>>{x.Name}"));
var appServicePlan = appServicePlanRequest.Body.Where(x => x.Name.Equals(servicePlanName)).FirstOrDefault();
if (appServicePlan == null)
{
log.LogError("Could not find app service plan.");
}

//scale up/down
//servicePlan.Sku.Family = "P";
//servicePlan.Sku.Name = "P1v2";
//servicePlan.Sku.Size = "P1v2";
//servicePlan.Sku.Tier = "PremiumV2";
servicePlan.Sku.Capacity = capacity; // scale out: number of instances
var updateResult = webSiteManagementClient.AppServicePlans.CreateOrUpdateWithHttpMessagesAsync(resourceGroup, servicePlanName, servicePlan).Result;
log.LogInformation("Completed!!");
return (ActionResult)new OkObjectResult($"Hello, {capacity} {updateResult.Response.StatusCode}");
}
else
{
return new BadRequestObjectResult("Please pass a capacity on the query string or in the request body");
}

}

HTH. By Jacky 2019-11-18

How to scale out web app base on custom metrics

Scenario: How to scale out web app base on custom metrics. For example: request, response time and etc.

Currently, Web App Linux only CPU and memory are available.

Here is the end to end steps/screenshots:

**Step 1. Use the portal to create an Azure AD application and service principal that can access resources

  1. Click “App registration”

  2. “New registration”

  3. Enter a Name and click Register

  4. In Overview page, you can find the Application Id and Tenant Id.

  5. Create a client secret.

  1. Copy the client secret in Azure Portal

For more information: https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal  

**Step 2. Add app roles in your application and receive them in the token

  1. Go to your resource group and “Access Control” and “Add a role assignment”

  2. Select “Contributor” and select your app (Registered in Step1)

  3. Go to your App Service Plan

  4. Select “Reader” Role

  5. Publish your function app.

  6. Test Your Function App in Azure Portal

  7. Verify the Instance Count in Azure Portal

  8. Get the Function URI:

  1. Test it in browser.

  2. Verify it in Azure Portal

For more information: https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps

**Step 3. Setup an alert to change capacity

  1. Create an alert and choose a custom metric

  2. Create a action group and action type “Webhook”, the function URI is from step2.8

  3. Save alert

  4. Simulate a User Load and verify the Instance Count:

  1. Instance Count changed to 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Microsoft.Azure.Management.ResourceManager.Fluent;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using Microsoft.Rest;
using System.Linq;
 
namespace ScaleWebAppFunction
{
    public static class Function1
    {
        [FunctionName("ScaleWebApp")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("C# HTTP trigger function processed a request.");
 
            int capacity;
 
            if (!string.IsNullOrEmpty(req.Query["capacity"]) && int.TryParse(req.Query["capacity"], out capacity))
            {
                log.LogInformation("You want to chagne the capacity to "+ capacity.ToString());
                // Step1. How to: Use the portal to create an Azure AD application and service principal that can access resources
                //      https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-create-service-principal-portal
 
                // Step2. How to: Add app roles in your application and receive them in the token
                //      https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-azure-ad-apps
                // For security concerns, I only give the resource group => contributor
                //                          and the service plan read permission
 
                var subscriptionId = "Your subscription id";
                var appId = "e6f6d231-Your-App_id";
                var secretKey = "2z]pj0vy2JRapjo:R@TJSoW1AmOCC=o8";
                var tenantId = "72f988bf-Your-Tenant-Id";
                var resourceGroup = "jackywebl1rg";
                var servicePlanName = "ASP-jackywebl1rg-81ee";
 
 
                // Step 3. change capacity
                var context = new AuthenticationContext("https://login.windows.net/" + tenantId);
                ClientCredential clientCredential = new ClientCredential(appId, secretKey);
                var tokenResponse = context.AcquireTokenAsync("https://management.azure.com/", clientCredential).Result;
                var accessToken = tokenResponse.AccessToken;
                TokenCredentials credential = new TokenCredentials(accessToken);
                var webSiteManagementClient = new Microsoft.Azure.Management.WebSites.WebSiteManagementClient(credential);
                webSiteManagementClient.SubscriptionId = subscriptionId;
                var servicePlan = webSiteManagementClient.AppServicePlans.ListByResourceGroupWithHttpMessagesAsync(resourceGroup).Result.Body.Where(x => x.Name.Equals(servicePlanName)).FirstOrDefault();
 
                var appServicePlanRequest = await webSiteManagementClient.AppServicePlans.ListByResourceGroupWithHttpMessagesAsync(resourceGroup);
                appServicePlanRequest.Body.ToList().ForEach(x => log.LogInformation($">>>{x.Name}"));
                var appServicePlan = appServicePlanRequest.Body.Where(x => x.Name.Equals(servicePlanName)).FirstOrDefault();
                if (appServicePlan == null)
                {
                    log.LogError("Could not find app service plan.");
                }
 
                //scale up/down
                //servicePlan.Sku.Family = "P";
                //servicePlan.Sku.Name = "P1v2";
                //servicePlan.Sku.Size = "P1v2";
                //servicePlan.Sku.Tier = "PremiumV2";
                servicePlan.Sku.Capacity = capacity; // scale out: number of instances
                var updateResult = webSiteManagementClient.AppServicePlans.CreateOrUpdateWithHttpMessagesAsync(resourceGroup, servicePlanName, servicePlan).Result;
                log.LogInformation("Completed!!");
                return (ActionResult)new OkObjectResult($"Hello, {capacity} {updateResult.Response.StatusCode}");
            }
            else
            {
                return new BadRequestObjectResult("Please pass a capacity on the query string or in the request body");
            }
           
        }
    }
}

HTH. 2019-11-18 By Jacky

How to deploy WAR file to Web App

How to deploy WAR file to Web App

To deploy a WAR file to App Service, send a POST request to https://clicktime.symantec.com/3BC7LTrhgToTsQqqjsFFuUi7Vc?u=https%3A%2F%2F%253Capp_name%253E.scm.azurewebsites.net%2Fapi%2Fwardeploy. The POST request must contain the .war file in the message body. The deployment credentials for your app are provided in the request by using HTTP BASIC authentication.
For the HTTP BASIC authentication, you need your App Service deployment credentials. To see how to set your deployment credentials, see Set and reset user-level credentials.

With cURL
The following example uses the cURL tool to deploy a .war file. Replace the placeholders , , and . When prompted by cURL, type in the password.
curl -X POST -u –data-binary @”https://clicktime.symantec.com/3BC7LTrhgToTsQqqjsFFuUi7Vc?u=https%3A%2F%2F%253Capp_name%253E.scm.azurewebsites.net%2Fapi%2Fwardeploy

For mor information:
https://docs.microsoft.com/en-us/azure/app-service/deploy-zip#deploy-war-file

You can check the extracted files in SCM:
Azure Portal -> Your Web App -> Advanced Tools -> Go

Debug Console -> CMD

Click the folder name to navigate to D:\home\site\wwwroot -> verify the extracted files

By default, wardeploy deploys to the ROOT app. Optionally, you can simply use the query parameter name to specify the name of the app you want to deploy to: /api/wardeploy?name=NAME_OF_APP. For example, /api/wardeploy?name=myapp will deploy to the /home/site/wwwroot/webapps/myapp directory instead of /home/site/wwwroot/webapps/ROOT.
For more information: https://github.com/projectkudu/kudu/wiki/Deploying-WAR-files-using-wardeploy

HTH. By Jacky. 2019-11-08

Zip deployment for Azure Functions

1
2
CURL Command
curl -k -X POST -u $pythonfuncapptestRG --data-binary @"C:\YourPath\pythoncode.zip" https://pythonfuncapptestrg.scm.yourdomain.com/api/zipdeploy

Powershell command:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Avoiding SSL Warning for invalid certificate
##############################################
add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
##############################################
# Force TLs 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

$username = "`$pythonfuncapptestRG"
$password = "YourPassword"
$filePath = "C:\YourPath\pythoncode.zip"
$apiUrl = "https://pythonfuncapptestrg.scm.yourdomain.com/api/zipdeploy"
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $username, $password)))
$userAgent = "powershell/1.0"
Invoke-RestMethod -Uri $apiUrl -Headers @{Authorization=("Basic {0}" -f $base64AuthInfo)} -UserAgent $userAgent -Method POST -InFile $filePath -ContentType "application/x-www-form-urlencoded" -Verbose -Debug

For more information:
Zip deployment for Azure Functions
https://docs.microsoft.com/en-us/azure/azure-functions/deployment-zip-push

HTH. 2019-11-5 By Jacky

Service Fabric Rest API to change Service Instance Count

PowerShell to achive it:

1
2
3
4
5
6
$clusterFQDN = "jackysf3.eastasia.cloudapp.azure.com"
$clusterEndpoint = $clusterFQDN+':19000'
$certThumbprint = (Get-ChildItem -Path Cert:\CurrentUser\My | where {$_.Subject -like "*$clusterFQDN*" }).Thumbprint
Connect-ServiceFabricCluster -ConnectionEndpoint $clusterEndpoint -KeepAliveIntervalInSec 10 -X509Credential -ServerCertThumbprint $certThumbprint -FindType FindByThumbprint -FindValue $certThumbprint -StoreLocation CurrentUser -StoreName My

Update-ServiceFabricService -Stateless fabric:/ServiceFabricApp1/Stateless1 -InstanceCount 5

Service Fabric Rest API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static async Task MainAsync()
{
Func<CancellationToken, Task<SecuritySettings>> GetSecurityCredentials = (ct) =>
{
// get the X509Certificate2 either from Certificate store or from file.
var clientCert = new System.Security.Cryptography.X509Certificates.X509Certificate2(@"C:\Users\jchiou\jackysf320191031213903.pfx", "Password01!");
var remoteSecuritySettings = new RemoteX509SecuritySettings(new List<string> { "8AB797576F2EF93574631A6BD7C5B381C86A60C4" });
return Task.FromResult<SecuritySettings>(new X509SecuritySettings(clientCert, remoteSecuritySettings));
};

// create client using ServiceFabricClientBuilder.UseX509Security
var client = new ServiceFabricClientBuilder()
.UseEndpoints(new Uri(@"https://jackysf3.eastasia.cloudapp.azure.com:19080"))
.UseX509Security(GetSecurityCredentials)
.BuildAsync().GetAwaiter().GetResult();

await client.Services.UpdateServiceAsync("ServiceFabricApp1~Stateless1", new StatelessServiceUpdateDescription(instanceCount: 5, defaultMoveCost: Microsoft.ServiceFabric.Common.MoveCost.Zero, flags: "1"));

var serviceDesc = await client.Services.GetServiceDescriptionAsync("ServiceFabricApp1~Stateless1");

}

Make sure to add “Flags” parameter, without it you will get HTTP 200 and nothing change.

Flags indicating whether other properties are set. Each of the associated properties corresponds to a flag, specified below, which, if set, indicate that the property is specified. This property can be a combination of those flags obtained using bitwise ‘OR’ operator. For example, if the provided value is 6 then the flags for ReplicaRestartWaitDuration (2) and QuorumLossWaitDuration (4) are set.

For more information:
https://docs.microsoft.com/en-us/rest/api/servicefabric/sfclient-model-statefulserviceupdatedescription#flags

https://github.com/microsoft/service-fabric-client-dotnet

Enjoy. 2019-11-1 By Jacky

REST-API-to-manage-and-find-data-with-Blob-Index-for-Azure-Storage.-(Preview)

How to get started
To enroll in the Blog Index preview, submit a request to register this feature to your subscription by running the following PowerShell or CLI commands:

Register by using PowerShell

1
2
3
Register-AzProviderFeature -FeatureName BlobIndex -ProviderNamespace Microsoft.Storage

Register-AzResourceProvider -ProviderNamespace Microsoft.Storage

Register by using Azure CLI

1
2
3
az feature register --namespace Microsoft.Storage --name BlobIndex

​az provider register --namespace 'Microsoft.Storage'

After your request is approved, any existing or new General-purpose v2 (GPv2) storage accounts in France Central and France South can leverage Blob Index’s capabilities. As with most previews, we recommend that this feature should not be used for production workloads until it reaches general availability.

For more information:
https://azure.microsoft.com/en-us/blog/manage-and-find-data-with-blob-index-for-azure-storage-now-in-preview/

REST API Sample:
I did a test in my lab and I can use the tag: x-ms-tags: “Date” > ‘2018-06-18’.

My request:

1
2
3
4
5
6
7
GET https://jackyst2.blob.core.windows.net/container1?comp=list&include=tags&restype=container 1.1

x-ms-version: 2019-10-10
x-ms-tags: "Date" > '2018-06-18'
x-ms-date: Wed, 03 Jun 2020 02:39:07 GMT
Authorization: SharedKey jackyst2:yourkey
Host: jackyst2.blob.core.windows.net

For more inforamtion:
https://review.docs.microsoft.com/en-us/azure/storage/blobs/storage-manage-find-blobs?tabs=azure-portal&branch=master#finding-data-using-blob-index-tags

HTH. 2020-June-3 By Jacky

Azure-Function-deletes-files-older-than-30-days

Azure Function deletes files older than 30 days.

Why need it? https://feedback.azure.com/forums/169385-web-apps/suggestions/39782458-web-app-application-logging-blob-retention

Sample Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
using System;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Auth;
using Microsoft.WindowsAzure.Storage.Blob;

namespace DeleteFilesOlderThanXDays
{
public static class DeleteFilesOlderThan30Days
{
[FunctionName("DeleteFilesOlderThan30Days")]
public async static Task Run([TimerTrigger("0 30 3 * * *")]TimerInfo myTimer, ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");

string accountName = "Your Storage Account Name";
string accountKey = "Your Account Access Key";

log.LogInformation("Storage Account Name: " + accountName);
var account = new CloudStorageAccount(new StorageCredentials(accountName, accountKey), true);
var blobClient = account.CreateCloudBlobClient();
//change it to your container name
var container = blobClient.GetContainerReference("Your Container Name");

log.LogInformation("Get a list of all blobs in your container");

BlobResultSegment result = await container.ListBlobsSegmentedAsync(null);

log.LogInformation("Iterate each blob");

int segmentSize = 10000;

BlobContinuationToken continuationToken = null;
CloudBlob blob;

try
{
// Call the listing operation and enumerate the result segment.
// When the continuation token is null, the last segment has been returned
// and execution can exit the loop.
do
{
BlobResultSegment resultSegment = await container.ListBlobsSegmentedAsync(string.Empty,
true, BlobListingDetails.Metadata, segmentSize, continuationToken, null, null);

foreach (var blobItem in resultSegment.Results)
{
// A flat listing operation returns only blobs, not virtual directories.
blob = (CloudBlob)blobItem;

// Write out some blob properties.
log.LogInformation("Blob name: {0} {1}", blob.Name, blob.Properties.LastModified);

log.LogInformation("Calculate when LastModified is compared to today");
TimeSpan? diff = DateTime.Today - blob.Properties.LastModified;
// 30 days, you can change it to 90 days and etc.
if (diff?.Days > 30)
{
log.LogInformation("Delete: " + blob.Name);
await blob.DeleteAsync();
}
}

log.LogInformation($"Foreach completed at: {DateTime.Now}");

// Get the continuation token and loop until it is null.
continuationToken = resultSegment.ContinuationToken;

} while (continuationToken != null);
}
catch (StorageException e)
{
log.LogInformation(e.Message);
}

log.LogInformation($"C# Timer trigger function completed at: {DateTime.Now}");
}
}
}

Change “Your Storage Account Name”, “Your Account Access Key”, “Your Container Name”, and Days.

Build and publish it to your Function App.

For more information:
Create a function in Azure that is triggered by a timer
https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-scheduled-function

Timer trigger for Azure Functions
https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer?tabs=csharp

RDP to Raspberry Pi 4

Install the xrdp package to the Raspberry Pi. This package will allow remote desktop on your Raspberry Pi.
sudo apt-get install xrdp

Retrieve the Raspberry Pi’s local IP address you can do that by using the following command.
hostname -I

My Raspberry Pi 4 is ready!!

How to retrieve blob copy status

az storage blob show is the right way to get blob copy status. The command uses “Get Blob Properties” rest operation, which is the recommended approach: https://docs.microsoft.com/en-us/rest/api/storageservices/copy-blob.

It will show the copy properties of the last copy operation.

PS Azure:> az storage blob show –container-name dest1 –account-key “YourAccountKey” –account-name jackyst3 –name “MyVHD.vhd”

{
  "content": "",
  "deleted": false,
  "metadata": {},
  "name": "MyVHD.vhd",
  "properties": {
    "appendBlobCommittedBlockCount": null,
    "blobTier": "Hot",
    "blobTierChangeTime": null,
    "blobTierInferred": true,
    "blobType": "BlockBlob",
    "contentLength": 0,
    "contentRange": null,
    "contentSettings": {
      "cacheControl": null,
      "contentDisposition": null,
      "contentEncoding": null,
      "contentLanguage": null,
      "contentMd5": null,
      "contentType": "application/octet-stream"
    },
    "copy": {
      "completionTime": null,
      "id": "a447e2f4-52bc-43e3-adfb-140c341b1208",
      "progress": "201326592/570770310",
      "source": "https://jackyst2.blob.core.windows.net/test/MyVHD.vhd?sp=r&st=2019-10-03T05:46:48Z&se=2019-10-03T13:46:48Z&spr=https&sv=2018-03-28&sig=nmQMfb6JmbgBdKSCmpifpDIn%2BZGHX9E4PFLWK1Gd%2B%2Bg%3D&sr=b",
      "status": "pending",
      "statusDescription": null
    },
    "creationTime": "2019-10-03T05:47:51+00:00",
    "deletedTime": null,
    "etag": "\"0x8D747C53A6C50CB\"",
    "lastModified": "2019-10-03T05:47:51+00:00",
    "lease": {
      "duration": null,
      "state": "available",
      "status": "unlocked"
    },
    "pageBlobSequenceNumber": null,
    "pageRanges": null,
    "remainingRetentionDays": null,
    "serverEncrypted": true
  },
  "snapshot": null
}
'''

PS Azure:\> az storage blob show --container-name dest1 --account-key "YourAccountKey" --account-name jackyst3  --name "MyVHD.vhd"
```json
{
  "content": "",
  "deleted": false,
  "metadata": {},
  "name": "MyVHD.vhd",
  "properties": {
    "appendBlobCommittedBlockCount": null,
    "blobTier": "Hot",
    "blobTierChangeTime": null,
    "blobTierInferred": true,
    "blobType": "BlockBlob",
    "contentLength": 570770310,
    "contentRange": null,
    "contentSettings": {
      "cacheControl": null,
      "contentDisposition": null,
      "contentEncoding": null,
      "contentLanguage": null,
      "contentMd5": null,
      "contentType": "application/octet-stream"
    },
    "copy": {
      "completionTime": "2019-10-03T05:49:50+00:00",
      "id": "a447e2f4-52bc-43e3-adfb-140c341b1208",
      "progress": "570770310/570770310",
      "source": "https://jackyst2.blob.core.windows.net/test/MyVHD.vhd?sp=r&st=2019-10-03T05:46:48Z&se=2019-10-03T13:46:48Z&spr=https&sv=2018-03-28&sig=nmQMfb6JmbgBdKSCmpifpDIn%2BZGHX9E4PFLWK1Gd%2B%2Bg%3D&sr=b",
      "status": "success",
      "statusDescription": null
    },
    "creationTime": "2019-10-03T05:47:51+00:00",
    "deletedTime": null,
    "etag": "\"0x8D747C58189F762\"",
    "lastModified": "2019-10-03T05:49:50+00:00",
    "lease": {
      "duration": null,
      "state": "available",
      "status": "unlocked"
    },
    "pageBlobSequenceNumber": null,
    "pageRanges": null,
    "remainingRetentionDays": null,
    "serverEncrypted": true
  },
  "snapshot": null
}
'''

For more information:
https://github.com/Azure/azure-cli/issues/1787

HTH. Jacky 2019-10-3