Azure API Management Report API using Powershell - powershell

I'm trying to execute APIM Report API using Powershell but it doesn't works.
Here's powershell code.
$gen_sas_code = #"
using System;
using System.Text;
using System.Globalization;
using System.Security.Cryptography;
public class Sas
{
public static void GenSas()
{
var id = "integration";
var key = "myprimarykey";
var expiry = DateTime.UtcNow.AddDays(10);
using (var encoder = new HMACSHA512(Encoding.UTF8.GetBytes(key)))
{
var dataToSign = id + "\n" + expiry.ToString("O", CultureInfo.InvariantCulture);
var hash = encoder.ComputeHash(Encoding.UTF8.GetBytes(dataToSign));
var signature = Convert.ToBase64String(hash);
var encodedToken = string.Format("SharedAccessSignature uid={0}&ex={1:o}&sn={2}", id, expiry, signature);
Console.WriteLine(encodedToken);
}
}
}
"#
Add-Type -TypeDefinition $gen_sas_code -Language CSharp
$sas = [Sas]::GenSas()
$interval = "PT15M"
$start_time = "'2017-10-16T04:02:00'"
$end_time = "'2017-10-16T04:15:00'"
$report_url = "https://myapim.management.azure-api.net/reports/byTime?api-version=2017-03-01&interval=$interval&`$filter=timestamp+ge+datetime$start_time+and++timestamp+le+datetime$end_time"
$report_headers = #{ "Authorization" = "$sas" };
Invoke-RestMethod -Method Get -Uri $report_url -Headers $report_headers
I referred this document about report API (https://learn.microsoft.com/en-us/rest/api/apimanagement/apimanagementrest/azure-api-management-rest-api-report-entity#ReportByTime).
Here's error response.
*The remote server returned error: (401) Unzuthorized
Invoke-RestMethod : リモート サーバーがエラーを返しました: (401) 許可されていません*
発生場所 C:\sas.ps1:56 文字:1
+ Invoke-RestMethod -Method Get -Uri $report_url -Headers $report_headers
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-RestMethod]、WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeRestMethodCommand
So, I consider that the powershell code has some problems because I have already confirmed that the SAS which is generated by above code works correctly by executing Report API using curl command.
Do you have any ideas?

It's a slightly annoying issue, but it comes down to newline handling in Powershell, the \n in the .net sample is referring to a newline, so you have to use the native way PowerShell declares a newline with a backtick instead, so do this:
var dataToSign = id + "`n" + expiry.ToString("O", CultureInfo.InvariantCulture);
... instead of:
var dataToSign = id + "\n" + expiry.ToString("O", CultureInfo.InvariantCulture);

Try using this script to generate the token https://github.com/Azure/api-management-samples/blob/master/scripts/Get_AzureRmApiManagementManagementToken.ps1

Related

Call Power BI API in Power Query

I'm trying to retrieve data from Power BI API services and the same is being accomplished via PowerShell code. But unfortunately due to some circumstances I am not able to deploy it in production. Hence now I am trying to get the same thing done in Power BI desktop itself so that I can call power BI rest API from Power Query only. There are tons of blogpost about calling an API in power query but they all require Power BI App registered Client ID. Which I don't have. I'm successfully able to call with my user name password in PowerShell and even I'm getting response from API.
Please find below PowerShell code and let me know if we can replicate the same in Power Query.
# User credential
$User = 'shahab***#*****.com'
$Pword = ConvertTo-SecureString –String '***password***' –AsPlainText -Force
$Credential = New-Object –TypeName System.Management.Automation.PSCredential –ArgumentList $User,$Pword
# Connect to service
Login-PowerBIServiceAccount -Credential $Credential
#Get Bearer token
$headers = Get-PowerBIAccessToken
$uri = 'https://api.powerbi.com/v1.0/myorg/datasets/f52f2abc-6445-41ee-ce02-3908c6e18dd4/refreshes'
$refreshes = Invoke-RestMethod -Uri $uri -Headers $headers -Method GET
$xs= $refreshes
Thanks.
With the wrapper, you want something like this.
WebRequest_Simple(
"https://api.powerbi.com",
"v1.0/myorg/datasets/f52f2abc-6445-41ee-ce02-3908c6e18dd4/refreshes",
[
Headers = [ Authorization = "Bearer OAuthTokenHere" ]
]
)
If the url had a query string (the part after ?), then you'd use options[Query]
Web Response
Full Query
Source: WebRequest_Simple.pq
let
/*
Wrapper for Web.Contents returns response metadata
for options, see: <https://learn.microsoft.com/en-us/powerquery-m/web-contents#__toc360793395>
*/
WebRequest_Simple
= (
base_url as text,
optional relative_path as nullable text,
optional options as nullable record
)
as record =>
let
headers = options[Headers]?, //or: ?? [ Accept = "application/json" ],
merged_options = [
Query = options[Query]?,
RelativePath = relative_path,
ManualStatusHandling = options[ManualStatusHandling]? ?? { 400, 404 },
Headers = headers
],
bytes = Web.Contents(base_url, merged_options),
response = Binary.Buffer(bytes),
response_metadata = Value.Metadata( bytes ),
status_code = response_metadata[Response.Status]?,
json = Json.Document(response),
Final = [
request_url = metadata[Content.Uri](),
status_code = status_code,
metadata = response_metadata,
IsJson = not (try json)[HasError],
response = response,
json = json
]
in
Final,
tests = {
WebRequest_Simple("https://httpbin.org", "json"), // expect: json
WebRequest_Simple("https://www.google.com"), // expect: html
WebRequest_Simple(
"https://api.powerbi.com",
"v1.0/myorg/datasets/f52f2abc-6445-41ee-ce02-3908c6e18dd4/refreshes",
[
Headers = [ Authorization = "Bearer OAuthTokenHere" ]
]
)
},
FinalResults = Table.FromRecords(tests,
type table[
status_code = text, request_url = text, metadata = record,
response = binary, IsJson = logical, json = any],
MissingField.Error
)
in
FinalResults

AWS Cloudfront files' invalidation via REST API and Powershell

I'm trying to write a PowerShell script to invalidate an object of AWS Cloudfront distribution (just a specific file), and not sure how to produce the "signed URL" they're asking for.
My code so far is:
$authPref = "AWS4-HMAC-SHA256"
$AWSAccessKey = "xxx"
$AWSSecretKey = "xxx"
$awsDateOnly = (Get-Date).AddHours(-3).ToString("yyyyMMdd")
$awsRegion = "us-east-1"
$awsServiceName = "cloudfront"
$awsRequestType = "aws4_request"
$stringToSign = $authPref + " " + $awsCallerReference + " " + $awsDateOnly + "/" + $awsRegion + "/" + $awsServiceName + "/" + $awsRequestType + " SOME_STRING_NOT_SURE_WHAT"
$hmacsha = New-Object System.Security.Cryptography.HMACSHA256
$hmacsha.key = [Text.Encoding]::ASCII.GetBytes($stringToSign)
$awsHMAC = $hmacsha.ComputeHash([Text.Encoding]::ASCII.GetBytes($AWSSecretKey))
$awsHMAC = [Convert]::ToBase64String($awsHMAC)
$awsSignedToken = $authPref + " Credential=" + $AWSAccessKey + "/" + $awsDateOnly + "/" + $awsRegion + "/" + $awsServiceName + "/" + $awsRequestType + ", SignedHeaders=content-type;host;x-amz-date, Signature=" + $awsHMAC
#POST /2017-03-25/distribution/$awsDistributionID/invalidation HTTP/1.1
$awsDistributionID = "xxx"
$awsCallerReference = (Get-Date).AddHours(-3).ToString("yyyyMMdd'T'HHmmss'Z'")
$invalidateObjectXML = #"
<?xml version="1.0" encoding="UTF-8"?>
<InvalidationBatch xmlns="http://cloudfront.amazonaws.com/doc/2017-03-25/">
<CallerReference>$awsCallerReference</CallerReference>
<Paths>
<Items>
<Path>/</Path>
</Items>
<Quantity>1</Quantity>
</Paths>
</InvalidationBatch>
"#
[xml]$invalidateObjectXML = $invalidateObjectXML
$awsCFuri = "https://cloudfront.amazonaws.com/2017-03-25/distribution/$awsDistributionID/invalidation"
Invoke-WebRequest -Method POST `
-Uri $awsCFuri `
-Headers #{"content-type"="text/xml";
"x-amz-date"="$awsCallerReference";
"authorization"="$awsSignedToken";
"host"="cloudfront.amazonaws.com"} `
-Body $invalidateObjectXML
The response is:
<ErrorResponse xmlns="http://cloudfront.amazonaws.com/doc/2017-03-25/"><Error><Type>Sender</Type><Code>SignatureDoesNotMatch</Code><Message>The request
signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation
for details.
The Canonical String for this request should have been
'POST
/2017-03-25/distribution/blabla/invalidation
content-type:text/xml
host:cloudfront.amazonaws.com
x-amz-date:20170503T203650Z
content-type;host;x-amz-date
blabla'
The String-to-Sign should have been
'AWS4-HMAC-SHA256
20170503T203650Z
20170503/us-east-1/cloudfront/aws4_request
blabla'
</Message></Error><RequestId>123-123</RequestId></ErrorResponse>
At line:1 char:1
So obviously I'm doing something wrong with the signed URL string that I do, but what it is?
Couldn't find any examples on the internet (not AWS docs nor any other blog) that demonstrates it in Powershell.
Thanks
AWS has developed a PowerShell module to interact with the AWS API called the AWS Tools for PowerShell. This module specifically handles request building and signing for you, so this method of calling to the raw API becomes unnecessary.
You can use this specifically to invalidate objects in a CloudFront distribution with the New-CFInvalidation cmdlet. Write the paths you want to invalidate to the Paths_Item parameter.
Signature:
New-CFInvalidation
-DistributionId <String>
-InvalidationBatch_CallerReference <String>
-Paths_Item <String[]>
-Paths_Quantity <Int32>
-Force <SwitchParameter>
Further Reading
AWS Documentation - Invalidating Objects and Displaying Information about Invalidations
AWS Documentation - New-CFInvalidation Cmdlet

Powershell 3.0 Invoke-WebRequest HTTPS Fails on All Requests

I am trying to work with our Load Balancer via Powershell 3.0 and a REST API. However I am currently getting a failure no matter what I try if it is an https request, whether to our load balancer or to any other https site. I feel like I'm missing something obvious.
Here is the code that fails with https
try
{
#fails
#$location='https://www.bing.com'
#fails
#$location='https://www.google.com'
#fails
#$location='https://www.facebook.com'
#fails
#$location='https://www.ebay.com'
#works
#$location='http://www.bing.com'
#works
#$location='http://www.google.com'
#fails (looks like Facebook does a redirect to https://)
$location='http://www.facebook.com'
#works
#$location='http://www.ebay.com'
$response=''
$response = Invoke-WebRequest -URI $location
$response.StatusCode
$response.Headers
}
catch
{
Write-Host StatusCode $response.StatusCode
Write-Host $_.Exception
}
The error I get is:
System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a send. ---> System.Management.Automation.PSInvalidOperationException:
There is no Runspace available to run scripts in this thread. You can provide one in the DefaultRunspace property of the System.Management.Automation.Runspaces.Runspa
ce type. The script block you attempted to invoke was: $true
at System.Net.TlsStream.EndWrite(IAsyncResult asyncResult)
at System.Net.ConnectStream.WriteHeadersCallback(IAsyncResult ar)
--- End of inner exception stack trace ---
at Microsoft.PowerShell.Commands.WebRequestPSCmdlet.GetResponse(WebRequest request)
at Microsoft.PowerShell.Commands.WebRequestPSCmdlet.ProcessRecord()
I was hoping this page and the suggestions towards the bottom including the one from Aaron D.) would make a difference but none of them made a difference.
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
and
function Ignore-SSLCertificates
{
$Provider = New-Object Microsoft.CSharp.CSharpCodeProvider
$Compiler = $Provider.CreateCompiler()
$Params = New-Object System.CodeDom.Compiler.CompilerParameters
$Params.GenerateExecutable = $false
$Params.GenerateInMemory = $true
$Params.IncludeDebugInformation = $false
$Params.ReferencedAssemblies.Add("System.DLL") > $null
$TASource=#'
namespace Local.ToolkitExtensions.Net.CertificatePolicy
{
public class TrustAll : System.Net.ICertificatePolicy
{
public bool CheckValidationResult(System.Net.ServicePoint sp,System.Security.Cryptography.X509Certificates.X509Certificate cert, System.Net.WebRequest req, int problem)
{
return true;
}
}
}
'#
$TAResults=$Provider.CompileAssemblyFromSource($Params,$TASource)
$TAAssembly=$TAResults.CompiledAssembly
## We create an instance of TrustAll and attach it to the ServicePointManager
$TrustAll = $TAAssembly.CreateInstance("Local.ToolkitExtensions.Net.CertificatePolicy.TrustAll")
[System.Net.ServicePointManager]::CertificatePolicy = $TrustAll
}
and
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
I have tried switching to Invoke-RestCommand but to no avail as I get the same response.
It feels like this has to be something environmental because I can't believe the above doesn't work for anyone else, but I've tried it on a workstation and on a server with the same results (doesn't rule out environment completely but I know they were set up differently).
Any thoughts?
This worked perfectly for me. The site defaults to TLS 1.0 and apparently PS doesn't work with that. I used this line:
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
My PS scripts (so far all I've tested) have worked perfectly.
The answer is do not do this to solve the SSL issue:
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
If you do this, your first https request will work (it seems), however subsequent ones will not. Additionaly at that point you need to close out of the Powershell ISE, and reopen it and then try again (without that line).
This is alluded to in a sentence here http://social.technet.microsoft.com/Forums/windowsserver/en-US/79958c6e-4763-4bd7-8b23-2c8dc5457131/sample-code-required-for-invokerestmethod-using-https-and-basic-authorisation?forum=winserverpowershell - "And all subsequent runs produce this error :", but it wasn't clear what the solution to reset was.
I too was plagued by this for a really long time. It even affected Visual Studio as VS loaded my $PROFILE into it's domain when running NuGet restore.
Seeing your comment above made me realize that I had a custom callback script because of one of our vendors shipped a product with an invalid CN in it's ssl cert.
Long story short, I replaced my script delegate with a compiled c# object (removing the script runspace from the equation).
(separate code block for C# highlighting)
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
public static class CustomCertificateValidationCallback {
public static void Install()
{
ServicePointManager.ServerCertificateValidationCallback += CustomCertificateValidationCallback.CheckValidationResult;
}
public static bool CheckValidationResult(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
// please don't do this. do some real validation with explicit exceptions.
return true;
}
}
In Powershell:
Add-Type "" # C# Code
[CustomCertificateValidationCallback]::Install()
Consolidating and condensing some of the above learnings, I have adopted the following approach:
Syntax colored and commented like the C# of yore:
// Piggyback in System.Net namespace to avoid using statement(s)
namespace System.Net
{
// Static class to make the ps call easy
// Uses a short name that is unlikely to clash with real stuff...YMMV
public static class Util
{
// Static method for a static class
public static void Init()
{
// [optionally] clear any cruft loaded into this static scope
ServicePointManager.ServerCertificateValidationCallback = null;
// Append a dangerously permissive validation callback
// using lambda syntax for brevity.
ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, errs) => true;
// Tell SPM to try protocols that have a chance
// of working against modern servers.
// Word on the street is that these will be tried from "most secure"
// to least secure. Some people add em all!
ServicePointManager.SecurityProtocol =
SecurityProtocolType.Tls |
SecurityProtocolType.Tls11 |
SecurityProtocolType.Tls12;
}
}
}
And now the real powershell highlighted version (no comments, but the same code)
Add-Type -Language CSharp #"
namespace System.Net {
public static class Util {
public static void Init() {
ServicePointManager.ServerCertificateValidationCallback = null;
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, errs) => true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
}}}"#
[System.Net.Util]::Init()
Obviously you can remove irrelevant whitespace, but you should be able to drop that into your session, and then Invoke-WebRequest at will.
Note that the
# Do not use IMHO!
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
approach seems quite incorrect for ps 5.1 (where i have tested this). Not sure where it came from, but I wish I had avoided it and saved the heartache.
The below powershell script works for me to check post web request
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;
}
}
"#
$AllProtocols = [System.Net.SecurityProtocolType]'Ssl3,Tls,Tls11,Tls12'
[System.Net.ServicePointManager]::SecurityProtocol = $AllProtocols
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
$uri = "XXXX"
$person = #{grant_type= 'user_password'
username = 'XXXX'
password = 'XXX'
}
$body = (ConvertTo-Json $person)
$hdrs = #{}
$hdrs.Add("XXXX","XXXX")
Invoke-RestMethod -Uri $uri -Method Post -Body $body -ContentType 'application/json' -Headers $hdrs

How do I instantiate CorRuntimeHost from mscoree.tlb in PowerShell?

I want to enumerate all the AppDomains in the current process from PowerShell. The process happens to be Visual Studio, which is hosting StudioShell. To do that I need to instantiate CorRuntimHost, which is part of mscoree.tlb, so I can adapt this C# code..
I tried to get the proper name of CorRunTimeHost and pass it to New-Object -COMObject "objectName". Based on this forum posting, I searched the registry and I think the correct name is CLRMetaData.CorRuntimeHost. However, while New-Object -ComObject 'CLRMetaData.CorRuntimeHost' -Strict does return an object, it only exposes the methods intrinsic to a COM object.
Based on this stackoverflow question I tried [Activator]::CreateInstance(). However, the following two statements give me the same problem as New-Object, namely I can't call the ICorRuntimeHost::EnumDomains() method.
$corRuntimeHost = [Activator]::CreateInstance([Type]::GetTypeFromProgID('CLRMetaData.CorRuntimeHost'));
$enumerator = $null;
$corRuntimeHost.EnumDomains([ref]$enumerator);
Method invocation failed because [System.__ComObject] doesn't contain a method named 'EnumDomains'.
At line:1 char:1
+ $corRuntimeHost.EnumDomains([ref]$enumerator)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : MethodNotFound
To get it working in PowerShell 3.0 I ended up having to use an AssemblyBuilder. Below is the working code:
The problem seems to be that there is no public constructor for mscoree.CorRuntimeHostClass in .NET 4.0 but there is in 3.5.
I later tested this on a Windows 7 VM with powershell 2.0 and now this code will work in PowerShell 2.0 and 3.0.
$tlbName = Split-Path -Parent ([AppDomain]::CurrentDomain.GetAssemblies() | Where { $_.Location -Match '\\mscorlib.dll$' }).Location
$tlbName = Join-Path $tlbName 'mscoree.tlb';
$csharpString = #"
//adapted from here http://blog.semanticsworks.com/2008/04/enumerating-appdomains.html
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
public class ListProcessAppDomains
{
[DllImport( `"oleaut32.dll`", CharSet = CharSet.Unicode, PreserveSig = false )]
private static extern void LoadTypeLibEx
(String strTypeLibName, RegKind regKind,
[MarshalAs( UnmanagedType.Interface )] out Object typeLib);
private enum RegKind
{
Default = 0,
Register = 1,
None = 2
}
private class ConversionEventHandler : ITypeLibImporterNotifySink
{
public void ReportEvent( ImporterEventKind eventKind, int eventCode, string eventMsg )
{
Console.Error.WriteLine("Kind: {0} Code: {1} Message");
}
public Assembly ResolveRef( object typeLib )
{
string stackTrace = System.Environment.StackTrace;
Console.WriteLine("ResolveRef ({0})", typeLib);
Console.WriteLine(stackTrace);
return null;
}
}
public static AssemblyBuilder LoadMsCoreeDll( ref Object typeLib ) {
ConversionEventHandler eventHandler = new ConversionEventHandler();
string assemblyName = "PoshComWrapper.dll";
LoadTypeLibEx( #"$($tlbName)", RegKind.None, out typeLib );
TypeLibConverter typeLibConverter = new TypeLibConverter();
return typeLibConverter.ConvertTypeLibToAssembly( typeLib, assemblyName, 0, eventHandler, null, null, null, null );
}
}
"#
# So we can run this scipt multiple times
try { [ListProcessAppDomains] } catch { Add-Type -TypeDefinition $csharpString }
function Get-AppDomain {
$typeLib = $null;
$assemblyBuilder = [ListProcessAppDomains]::LoadMsCoreeDll([ref] $typeLib)
$corRuntimeHostClass = $assemblyBuilder.CreateInstance('PoshComWrapper.CorRuntimeHostClass')
$enumHandle = [IntPtr]::Zero
$corRuntimeHostClass.EnumDomains([ref] $enumHandle);
$appDomain = $null;
do
{
$corRuntimeHostClass.NextDomain($enumHandle, [ref] $appDomain);
if ($appDomain -ne $null -and $appDomain.GetType() -eq [AppDomain]) { $appDomain; }
} while ($appDomain -ne $null)
}
Get-AppDomain

PowerShell error uploading blob text to Azure: UploadText(string)

I have a powershell module which attempts to upload a blob to azure storage. Everything checks out until the last line which actually uploads the blob.
I receive the following error:
Exception calling "UploadText" with "1" argument(s):
"The specified resource does not exist."
At line:1 char:1
+ $blob.UploadText("asdasdfsdf")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : StorageClientException
I have also tried using the overload with 3 args, but the same issue exists there as well.
Here is the module:
Function Add-BlobText
{
[CmdletBinding()]
param(
[Parameter(Mandatory = $true,Position = 0)]
[string]
$StorageAccount,
[Parameter(Mandatory = $true,Position = 1)]
[string]
$Container,
[Parameter(Mandatory = $true,Position = 2)]
[string]
$BlobName,
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[string]
$BlobText
) #end param
Add-Type -Path "C:\Assemblies\Microsoft.WindowsAzure.StorageClient.dll"
Set-AzureSubscription -SubscriptionName "MySubName"
$secondaryKey = (Get-AzureStorageKey -StorageAccountName $storageAccount).Secondary
$creds = New-Object Microsoft.WindowsAzure.StorageCredentialsAccountAndKey($StorageAccount,$secondaryKey)
$cloudStorageAccount = New-Object Microsoft.WindowsAzure.CloudStorageAccount($creds, $true)
[Microsoft.WindowsAzure.StorageClient.CloudBlobClient]$cloudBlobClient = New-Object Microsoft.WindowsAzure.StorageClient.CloudBlobClient($cloudStorageAccount.BlobEndpoint)
[Microsoft.WindowsAzure.StorageClient.CloudBlobContainer]$blobContainer = $cloudBlobClient.GetContainerReference($Container)
[Microsoft.WindowsAzure.StorageClient.CloudBlob]$blob = $blobContainer.GetBlobReference($BlobName)
$blob.UploadText($BlobText)
} #end Function Add-BlobText
Update:
I have been able to get this working as a binary module (below). If anyone can figure out why UploadText() works within a binary module but throws an exception in a script module, please let me know.
[Cmdlet(VerbsCommon.Add, "BlobText")]
public class AddBlobText : PSCmdlet
{
[Parameter(Mandatory = true, Position = 0)]
public string StorageAccount { get; set; }
[Parameter(Mandatory = true, Position = 1)]
public string Container { get; set; }
[Parameter(Mandatory = true, Position = 2)]
public string BlobName { get; set; }
[Parameter(Mandatory = true, ValueFromPipeline = true)]
public string BlobText { get; set; }
protected override void ProcessRecord()
{
PowerShell ps = PowerShell.Create();
ps.AddScript("Set-AzureSubscription -SubscriptionName 'MySubName'");
string keyScript = "( Get-AzureStorageKey -StorageAccountName " + StorageAccount + " ).Secondary";
ps.AddScript(keyScript);
Collection<PSObject> result = ps.Invoke();
string secondaryKey = result[0].ToString();
StorageCredentialsAccountAndKey credentials = new StorageCredentialsAccountAndKey(StorageAccount, secondaryKey);
CloudStorageAccount storageAccount = new CloudStorageAccount(credentials, true);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(Container);
var blob = container.GetBlobReference(BlobName);
blob.UploadText(BlobText);
}
}
This is probably because your container does not exist. You should call CreateIfNotExist after initializing the container to make sure it exists:
[Microsoft.WindowsAzure.StorageClient.CloudBlobContainer]$blobContainer = $cloudBlobClient.GetContainerReference($Container)
$blobContainer.CreateIfNotExist() <-- Here
[Microsoft.WindowsAzure.StorageClient.CloudBlob]$blob = $blobContainer.GetBlobReference($BlobName)
$blob.UploadText($BlobText)
This error is very ambiguous and misleading but there are instances' where Azure Storage can get "confused". Looking at Sandrino's example and specifically this line,
[Microsoft.WindowsAzure.StorageClient.CloudBlob]$blob = $blobContainer.GetBlobReference($BlobName)
Not that Sandrino's answer is your issue but the exception you encountered will happen when passing a Url or possibly other confusing key strings to Azure Storage Containers.
Unfortunately I am not a Powershell guy but here is a reproducing example then fix in C#.
public void Save(string objId, T obj)
{
CloudBlob blob = this.container.GetBlobReference(objId); // Problematic if a URL
blob.Properties.ContentType = "application/json";
var serialized = string.Empty;
serialized = serializer.Serialize(obj);
if (this.jsonpSupport)
{
serialized = this.container.Name + "Callback(" + serialized + ")";
}
blob.UploadText(serialized);
}
Assume that this.container is a valid blob storage instance pointing to http://127.0.0.1:10000/devstoreaccount1/sggames or whatever you have for a valid container.
And objId is a key that contains a Url like https://www.google.com/accounts/o8/id?id=AItOawk4Dw9sLxSc-zmdWQHdZNcyzkTcvKUkhiE ...and yes this can happen, in my case this is an actual identity claim from Google using Azure ACS.
After the GetBlobReference call the blob instance has become corrupt which now looks at a messed up Uri -> https://www.google.com/accounts/o8/id?id=AItOawk4Dw9sLxSc-zmdWQHdZNcyzkTcvKUkhiE
Unfortunately the solution to simply call $blobContainer.CreateIfNotExist() is not a solution and wouldn't work. Key's that contain a Uri structure will simply be used to re-interpret the blob storage location.
The work around (other than daredev's Update) would be something like this:
if (Uri.IsWellFormedUriString(claim, UriKind.Absolute) && HttpUtility.ParseQueryString(claim).Count > 0)
{
claim = HttpUtility.ParseQueryString(claim)[0];
}
Add this code within my method above to clean up any Uri's, but you could use any appropriate method like Base64 encoding URLs if you need to maintain the full key.
Here are the before and after images showing the results as I described.
The bad:
notice the bad URI
this bad URI munged up the actual storage blob location
here is the same exception daredev had
The good:
the new scrubbed key, notice it's just the value on the URL's query string
Azure Storage URI looks good now
Eureka!
Hope this helps.
This is the PowerShell script I use to upload a file to Azure Blob: Uploading to Azure Storage
$SubscriptionName = ""
$SubscriptionId = ""
$DestContainer = ""
$StorageAccountName = ""
Import-AzurePublishSettingsFile -PublishSettingsFile "<Location of the publishsettings-file>"
Set-AzureSubscription -SubscriptionId $SubscriptionId -CurrentStorageAccountName $StorageAccountName
Select-AzureSubscription -SubscriptionName $SubscriptionName
Set-AzureStorageBlobContent -File "<File you want to upload>" -Container $DestContainer