简体   繁体   中英

Calling a C# method with argument of type IEnumerable<string> in Powershell

I am using the Box.V2 SDK from PowerShell. I am calling the method documented here:

https://github.com/box/box-windows-sdk-v2/blob/master/Box.V2/Managers/BoxUsersManager.cs

This is my code:

# Load Assemblies
[Reflection.Assembly]::LoadFile("C:\Users\jfrank\Documents\BoxSDKV2\System.IdentityModel.Tokens.Jwt.5.1.4\lib\net45\System.IdentityModel.Tokens.Jwt.dll")
[Reflection.Assembly]::LoadFile("C:\Users\jfrank\Documents\BoxSDKV2\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll")
[Reflection.Assembly]::LoadFile("C:\Users\jfrank\Documents\BoxSDKV2\Box.V2.3.3.0\lib\net45\Box.V2.dll")
[Reflection.Assembly]::LoadFile("C:\Users\jfrank\Documents\BoxSDKV2\Microsoft.IdentityModel.Logging.1.1.4\lib\net45\Microsoft.IdentityModel.Logging.dll")
[Reflection.Assembly]::LoadFile("C:\Users\jfrank\Documents\BoxSDKV2\Microsoft.IdentityModel.Tokens.5.1.4\lib\net45\Microsoft.IdentityModel.Tokens.dll")
[Reflection.Assembly]::LoadFile("C:\Users\jfrank\Documents\BoxSDKV2\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll")
[Reflection.Assembly]::LoadFile("C:\Users\jfrank\Documents\BoxSDKV2\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll")

# Configure
$jsonConfig = Get-content "C:\Users\jfrank\Documents\WindowsPowerShell\Scripts\Box\config.json" | ConvertFrom-Json
$boxConfig = New-Object Box.V2.config.BoxConfig (($jsonConfig.boxAppSettings).clientID, ($jsonConfig.boxAppSettings).clientSecret, $jsonConfig.enterpriseID, (($jsonConfig.boxAppSettings).appAuth).privateKey, (($jsonConfig.boxAppSettings).appAuth).passphrase, (($jsonConfig.boxAppSettings).appAuth).publicKeyID)
$boxJWT = New-Object Box.V2.JWTAuth.BoxJWTAuth ($boxConfig)

# Authenticate
$adminToken = $boxJWT.AdminToken()
$adminClient = $boxJWT.AdminClient($adminToken)

# Get Users
$enterpriseUsers = $adminClient.UsersManager.GetEnterpriseUsersAsync().Result

# Get Admin by name
Foreach ($entry in $enterpriseUsers.Entries) {
    if ($entry.Name -eq "Johannes Frank") {
            $admin = $entry
        }
    }

# Get User
$userToken = $boxJWT.UserToken($admin.Id)
$userClient = $boxJWT.UserClient($userToken, $admin.Id)


$fields = [System.Collections.Generic.IEnumerable`1[System.String]]"role, enterprise"

$userClient.UsersManager.GetCurrentUserInformationAsync($fields).Result | Format-List

Without the parameter $fields the last line works perfectly. But calling with the parameter is tedious.

I am getting currently besides other tries the following error message:

GAC    Version        Location                                                                                                             
---    -------        --------                                                                                                             
False  v4.0.30319     C:\Users\jfrank\Documents\BoxSDKV2\System.IdentityModel.Tokens.Jwt.5.1.4\lib\net45\System.IdentityModel.Tokens.Jwt...
False  v1.1.4322      C:\Users\jfrank\Documents\BoxSDKV2\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll                                    
False  v4.0.30319     C:\Users\jfrank\Documents\BoxSDKV2\Box.V2.3.3.0\lib\net45\Box.V2.dll                                                 
False  v4.0.30319     C:\Users\jfrank\Documents\BoxSDKV2\Microsoft.IdentityModel.Logging.1.1.4\lib\net45\Microsoft.IdentityModel.Logging...
False  v4.0.30319     C:\Users\jfrank\Documents\BoxSDKV2\Microsoft.IdentityModel.Tokens.5.1.4\lib\net45\Microsoft.IdentityModel.Tokens.dll 
False  v4.0.30319     C:\Users\jfrank\Documents\BoxSDKV2\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll                              
False  v4.0.30319     C:\Users\jfrank\Documents\BoxSDKV2\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll                               
Cannot convert the "role, enterprise" value of type "System.String" to type "System.Collections.Generic.IEnumerable`1[System.String]".
At C:\Users\jfrank\Documents\WindowsPowerShell\Scripts\Box\BoxJWTAuth.ps1:34 char:1
+ $fields = [System.Collections.Generic.IEnumerable`1[System.String]]"r ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [], RuntimeException
    + FullyQualifiedErrorId : ConvertToFinalInvalidCastException

Cannot convert argument "fields", with value: "Box.V2.Models.BoxUser", for "GetCurrentUserInformationAsync" to type 
"System.Collections.Generic.IEnumerable`1[System.String]": "Cannot convert the "Box.V2.Models.BoxUser" value of type 
"Box.V2.Models.BoxUser" to type "System.Collections.Generic.IEnumerable`1[System.String]"."
At C:\Users\jfrank\Documents\WindowsPowerShell\Scripts\Box\BoxJWTAuth.ps1:36 char:1
+ $userClient.UsersManager.GetCurrentUserInformationAsync($fields).Resu ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodException
    + FullyQualifiedErrorId : MethodArgumentConversionInvalidCastArgument

Can someone tell me how to call this

public async Task<BoxUser> GetCurrentUserInformationAsync(IEnumerable<string> fields = null)

method with its parameter fields?

Edit: You'll find the answer down there: [string[]] works as the right typecast. There are two other ways.

Personal remark: But I really now value Python even more. And Python is not GW Basic. I have type security there too. Hours spent (wasted) to get a value into an argument. What kind of "security" did I gain in compenstation and still not fully grabbing the reason for "Generic Types"? I am going to run unit tests anyway. If anyone likes to comment on the advantage of this system, I am happy to learn.

I really cannot see how these questions are related. I had read this answer before and my initial try came from that answer. I spent (possibly wasted) hours in tries to do that single typecast. In python I would have just assigned and had the right type right away. Can you explain why these questions are related and what do I gain by this kind of type "security"?

You aren't gaining any security. You're creating a very specific type of object using a primitive generic class. There's a very narrow set of circumstances when you need to do that, which is why it isn't easy.

Look, I've never used box-windows-sdk-v2. However, your code tells me I need to load seven assemblies and have an existing JSON configuration file just to instance Box.V2.config.BoxConfig . I don't even have a Box user account. Understand that that's not reasonable for someone to set up just to test your code.

What I see is this error message:

"Cannot convert the "Box.V2.Models.BoxUser" value of type "Box.V2.Models.BoxUser" to type "System.Collections.Generic.IEnumerable`1[System.String]"

Now, your question and the way you asked led me to believe that you understood what IEnumerable<T> is. Now, however, I don't think you do, so you haven't tried what I would have already tried.

What @Andrei's answer is doing is building your own custom class that implements IEnumerable instead of using any of the existing classes that have already done so. However, based on the way you asked your question, that's what you asked for .

So, let's start over from the beginning:

IList<T> , ICollection<T> , and IEnumerable<T> are generic interfaces to objects. They're primitive types in C# that allow you to construct custom collection types. They're also implemented by the built in generic collection types like List<T> and Stack<T> and Dictionary<TKey, TValue> . This means that if you need to write a C# method that needs to enumerate a collection, you can write it using the the IEnumerable<T> type and it will be able to enumerate any collections of objects as long as that collection implements the IEnumerable<T> interface. Even System.Array was extended to include these types , with some specific caveats that some methods will fail:

Single-dimensional arrays implement the System.Collections.Generic.IList, System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IReadOnlyList and System.Collections.Generic.IReadOnlyCollection generic interfaces. The implementations are provided to arrays at run time, and as a result, the generic interfaces do not appear in the declaration syntax for the Array class. In addition, there are no reference topics for interface members that are accessible only by casting an array to the generic interface type (explicit interface implementations). The key thing to be aware of when you cast an array to one of these interfaces is that members which add, insert, or remove elements throw NotSupportedException.

So, what your error message says is, "I need an object that is a collection of System.String s that implements the IEnumerable<T> interface."

That means that I assumed you'd initially tried one of these:

$fields = "role", "enterprise"  # Creates a [System.Object[]], so should error

$fields = @("role", "enterprise")  # Creates a [System.Object[]], so should error

The default array in PowerShell is of type [System.Object[]] because PowerShell tries to be as generic as it possibly can be. PowerShell's commands all accept that type. However, when you're calling object methods from libraries you've loaded, you're really working in C# land, not in PowerShell. You've got to make objects that conform to C#'s requirements. That's why it feels like you're writing through a fog where the system doesn't make it clear what it wants. You're actually writing C# code (which tries to be statically typed) with PowerShell (which tries not to be statically typed).

The problem here is that there is no built-in way in the .Net Framework to automatically cast a [Object[]] to something that implements [IEnumerable[String]]. Just like you can't say 'Hello' + 1 + 'World' , in Python, the language doesn't know how to make the conversion for you.

When any of the above didn't work, you should have tried any one of these :

$fields = [System.String[]]("role", "enterprise")  # Creates a [System.String[]], so may work
# [System.String[]] implements [IEnumerable[String]], but not completely

$fields = [System.Collections.Generic.List[System.String]]("role", "enterprise")
# Creates a [List[String]], and should work because that implements [IEnumerable[String]]

$fields = New-Object -TypeName "System.Collections.Generic.List[System.String]"
$fields.Add('role')
$fields.Add('enterprise')
# Also creates a [List[String]]

$fields = New-Object -TypeName "System.Collections.Generic.Stack[System.String]"
$fields.Push('role')
$fields.Push('enterprise')
# Creates a [Stack[String]], and should work because that also implements [IEnumerable[String]]

$fields = New-Object -TypeName "System.Collections.Generic.HashSet[System.String]"
[void]$fields.Add('role')
[void]$fields.Add('enterprise')

# See also System.Collections.Generic.Queue, System.Collections.Generic.SortedSet, System.Collections.Generic.LinkedList, etc.

However I don't know what Box.V2.Models.BoxUser does, nor do I know exactly how picky it is with objects. It's possible that you actually need the primitive generic, and that's what I assumed because you'd gone through so much trouble to try to create it. That's clearly what the other answer here believed as well.

As far as to, "Why is PowerShell so complicated when Python is so simple?":

Well, you're not writing PowerShell in this script. You're not. PowerShell looks like this:

Get-ChildItem *.pdf -File | Where-Object {
    $_.LastWriteTime -lt $CutoffDate
} | Move-Item -Destination C:\archive\ -Verbose

What you're doing here is using PowerShell to write interpretive C#. You can do that because PowerShell gives you full access to the .Net Framework, but working with third party libraries is an advanced PowerShell topic. It's not at all supposed to be the first script you write. It's about 30% PowerShell and 70% C#, and you really need at least a working understanding of both because a lot of the conventions that make PowerShell fast and easy to work with don't work at all once you start calling third party libraries.

You have to understand that using PowerShell to do this will work, but it is not what the authors of any of your libraries intended you to use. PowerShell isn't C#. You will come across the odd library that doesn't implement things in a way that PowerShell works well with because by using PowerShell to do it you've automatically put yourself in a group of about 1% of the developers working with that library. The conventions of PowerShell will not 100% align with the conventions of C# and third party libraries.

&{ # About this http://community.idera.com/powershell/powertips/b/tips/posts/using-custom-scopes
  $OFS=', ' # About $OFS https://blogs.msdn.microsoft.com/powershell/2006/07/15/psmdtagfaq-what-is-ofs/
  $PSVersionTable # The Powershell version and not only https://stackoverflow.com/questions/1825585/determine-installed-powershell-version
# The way number one to takle the problem
  $fields = [activator]::createinstance([System.Collections.Generic.List``1].makegenerictype([System.String]))
  $fields.AddRange([string[]]("role", "enterprise"))
# The next three lines are needed just to show the result
  $fields.gettype().fullname
  $fields.gettype().getInterfaces() | ?{$_.Name.startswith('IEnumerable') -and $_.IsGenericType} | Select -ExpandProperty FullName
  """${fields}"""
  rv fields # rv is the alias of Remove-Variable cmdlet
# The way number two
  $fields = [System.Collections.Generic.List[System.String]][string[]]("role", "enterprise")
  $fields.gettype().fullname
  $fields.gettype().getInterfaces() | ?{$_.Name.startswith('IEnumerable') -and $_.IsGenericType} | Select -ExpandProperty FullName
  """${fields}"""
  rv fields
# The way number three
  $fields = [string[]]("role", "enterprise")
  $fields.gettype().fullname
  $fields.gettype().getInterfaces() | ?{$_.Name.startswith('IEnumerable') -and $_.IsGenericType} | Select -ExpandProperty FullName
  """${fields}"""
}
Name                           Value
CLRVersion                     2.0.50727.8762
BuildVersion                   6.1.7601.17514                                                                                                                                                                                          
PSVersion                      2.0
WSManStackVersion              2.0
PSCompatibleVersions           {1.0, 2.0}                                                                                                                                                                                                                          
SerializationVersion           1.1.0.1
PSRemotingProtocolVersion      2.1                                                                                                                                                                                                                                 
System.Collections.Generic.List`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
System.Collections.Generic.IEnumerable`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
"role, enterprise"
System.Collections.Generic.List`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
System.Collections.Generic.IEnumerable`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
"role, enterprise"
System.String[]
System.Collections.Generic.IEnumerable`1[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
"role, enterprise"

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM