Search code examples
typesazure-powershellpowershell-core

"InvalidOperation: Unable to find type [Azure.Response]." in Azure PS script


I'm writing a script using Azure PS and I am used to typing all variables explicitly as it helps me avoid surprises. I found that when I type the variables storing results of API calls returning Azure.Response<T> instances, PowerShell is unable to find the type.

When digging deeper into the objects returned by the Azure PS cmdlets, the APIs belonging to Azure SDK for .NET are exposed -- e.g. Get-AzStorageQueue returns Azure PS-specific AzureStorageQueue but its property QueueClient exposes an instance of the Azure SDK for .NET type QueueClient.

PS > [Azure.Storage.Queues.QueueClient]

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    QueueClient                              System.Object

So far so good. But the methods of the client often return instances of Azure.Response<T>. E.g., QueueClient.Exists() returns Azure.Response<bool>, QueueClient.PeekMessage() returns Azure.Response<Azure.Storage.Queues.Models.PeekedMessage>, etc. When trying to work with that type in PowerShell, I am getting the error mentioned in the title of this question:

PS > [Azure.Response[bool]]
InvalidOperation: Unable to find type [Azure.Response].
PS > [Azure.Response`1]
InvalidOperation: Unable to find type [Azure.Response`1].
PS > [Azure.Response]
InvalidOperation: Unable to find type [Azure.Response].

The actual code I am trying to get to work, simplified and made self-contained:

#require -Modules Az
using namespace Microsoft.WindowsAzure.Commands.Common.Storage.ResourceModel
using namespace Microsoft.WindowsAzure.Commands.Storage.Common

Connect-AzAccount
[AzureStorageContext]$context = New-AzStorageContext -StorageAccountName mystorage -UseConnectedAccount
[AzureStorageQueue]$queue = Get-AzStorageQueue -Context $context | Select-Object -First 1
[Azure.Response[bool]]$existsResponse = $queue.QueueClient.Exists()
Write-Output $existsResponse.Value

The error is:

InvalidOperation: C:\Users\Palec\AppData\Local\Temp\script.ps1:8
Line |
   8 |  [Azure.Response[bool]]$existsResponse = $queue.QueueClient.Exists()
     |  ~~~~~~~~~~~~~~~~~~~~~~
     | Unable to find type [Azure.Response].

When I inspect the result of the Exists() call, it is of an internal subtype of the public Azure.Response<bool> type, so the typing of the variable seems to be correct:

PS > $queue.QueueClient.Exists().GetType() | Format-List -Property FullName,Assembly,{$_.BaseType.FullName},{$_.BaseType.Assembly}

FullName             : Azure.ValueResponse`1[[System.Boolean, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
Assembly             : Azure.Core, Version=1.44.1.0, Culture=neutral, PublicKeyToken=92742159e12e44c8
$_.BaseType.FullName : Azure.Response`1[[System.Boolean, System.Private.CoreLib, Version=9.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]
$_.BaseType.Assembly : Azure.Core, Version=1.44.1.0, Culture=neutral, PublicKeyToken=92742159e12e44c8

I believe it is not just about generics being somewhat problematic in PowerShell, because when I try to access a non-generic type from the same Azure.Core assembly, namely Azure.ETag, I get the same error:

PS > [Azure.ETag]
InvalidOperation: Unable to find type [Azure.ETag].

I even tried explicitly loading the Azure.Core assembly using Add-Type but it did not help. However, it can be loaded and contains the right types.

PS > Add-Type -AssemblyName Azure.Core -PassThru | Where-Object { $_.Name -contains "Response" -or $_.Name -contains "ETag" }

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    ETag                                     System.ValueType
True     False    Response                                 System.Object

What am I doing wrong? I am using PowerShell 7.5.0 and Azure PS 13.2.0, if that makes any difference.


Solution

  • The Az.Storage module loads the Azure.Core assembly in a custom assembly load context and PowerShell does not search assemblies loaded in a custom assembly load context for types.

    That behavior is by design -- such assemblies are supposed to be isolated.

    A workaround is to use assembly-qualified name of the type, i.e.:

    [Azure.ETag, Azure.Core]
    [Azure.Response`1[bool], Azure.Core]
    [Azure.Response`1[PeekedMessage], Azure.Core] # Relies on `using namespace Azure.Storage.Queues.Models`
    [Azure.Response`1[[Azure.ETag, Azure.Core]], Azure.Core]
    

    Then, the Type.GetType(string, bool, bool) call internally used by PowerShell is somehow able to locate the assembly even in the custom assembly load context. However, you must not omit the namespace (using namespace Azure is not applied to assembly-qualified names) and you must not forget that the number of type parameters (the ...`1 in the examples above) is a part of the assembly-qualified name even for closed generics. In type parameters, you may use the normal way of referencing types unless the type itself suffers from the assembly load context issue.

    See GitHub Azure/azure-powershell issue #21134 for more details.