Someone on the newsgroup ran into an issue whereby a instance of a non CLS Compliant type was fed to PowerShell's default formatter. The type in question had two properties with the same name, but in different casing. I'm pretty much just going to carbon-copy the qestion and answer here as I feel this is worth preserving in the annals of blogdom.
> I am in the process of writing a script to document SharePoint
> installations. When I try to get information about the content database
> I get this error.
> "out-lineoutput : The field/property: "Id" for type:
> "Microsoft.SharePoint.Administration.SPContentDatabase" differs only in
> case from the field/property: "ID". Failed to use non CLS compliant type."
> Here is some of the PowerShell Script that I am using
> $site = New-Object Microsoft.SharePoint.SPSite("http://localhost")
> It is the final line that is giving me the error. I can get other
> properties without error. Can someone enlighten me on this error
> message and let me know if there is a way to work around it.
This is a tricky one for people very new to powershell without any real experience of the .NET framework. One of the rules of a so-called "CLS compliant type" (cls = common lanaguage specification) is that you should not expose any public members that only differ in casing. The SPContentDatabase type has two properties, "Id" and "ID." This is perfectly legal in C# since case in important in that language and the compiler sees them as different as chalk and cheese. However, in VB.NET, case is not important and as such it [vb] has no native mechanism to differentiate between the two. The same goes for PowerShell's grammar. So, how do we get around this?
First, you grab a handle to the method directly by asking for it from the Type itself:
PS> $idMethod1 = [Microsoft.SharePoint.Administration.SPContentDatabase].getmethod("get_ID")
and for the second version,
PS> $idMethod2 = [Microsoft.SharePoint.Administration.SPContentDatabase].getmethod("get_Id")
Note the differing case for get_Id and get_ID. Btw, Properties like ID in .NET are actually served by special accessor methods prefixed with get_ and set_, hence the prefixes. Next, you need a way to invoke those methods on the instance itself: this is done by using the Invoke method on the MethodInfo object representing the method itself. You pass the instance to the Invoke method telling it that the method is "instance" (e.g. not static/shared) and "public" and it will call that method for you on the instance, returning the value, if any.
PS> $result = $idMethod1.Invoke($site.ContentDatabase, "instance,public", $null, $null, $null)
I know from the SharePoint documenation, that this property returns a guid. Also, due to another oversight, Posh does not know how to format Guids, so you must call ToString() on the result to get the value back or you'll be greeted by a blank line from powershell's formatter.
Bizarrely, these two properties return the same guid, but hey, I'll let someone from the sharepoint team answer that one. Anyway, you should have enough information here to extract the properties that you need.
So, that's the interesting portion out of the way. Another way to do this might be to create a custom view for the SPContentDatabase type omitting the duplicated property and use the Update-FormatData cmdlet to activate it. This may not work though, as the non-cls compliancy issue may rear it's ugly head before posh's formatter gets this far.
Hope this helps,
- Oisin / x0n.
A question came up on the PowerShell newsgroup concerning how to use enums, particularly the shortcut form whereby posh will coerce strings. Roman Kuzmin of PowerShell/FarNet fame offered up a quick answer on how to provide multiple values to be "or'd" together for [flags] decorated enums:
To which the original poster (moff) replied:
Out of interest, can this syntax be used for xor'ing values together too?
At the moment my statement looks like this:
-bor [Microsoft.SqlServer.Replication.scriptoptions]::IncludeAll `
-bxor [Microsoft.SqlServer.Replication.scriptoptions]::IncludeReplicationJobs ))
To which I explained that sure, one way has you casting the operands (using system.attributetargets as the enum example):
$targets = ([attributetargets]"all" -bxor [attributetargets]"event,field")
And if the type name is long, another handy trick is to assign the enum type to a variable, e.g.
$enum = [Microsoft.SqlServer.Replication.ScriptOptions]
$options = ($enum::creation -bor $enum::IncludeAll) -bxor $enum::includereplicationjobs
...and finally, if you want to cast multiple flags using a variable shortcut, use the -as operator:
$options = $enum::all -bxor ("includeall,includereplicationjobs" -as $enum)
because [$enum]"creation,includeall" (or other guessed-at variants) won't work.
Holy poop! This is huge!
I know Scott was leading the charge in a general source code cleanup which started some times ago, but it looks like they're nearly done! This is amazing - from within VS 2008 (or your favourite source code editor), you'll be able to seemlessly single-step debug from your own code in that of the .NET framework itself.
Bravo Microsoft! This is an incredibly symbolic step!