ValueFromPipelineByPropertyName failing on module cmdlet - powershell

I have a cmdlet called Get-Organization which returns the below as return type
public class OrgModel
{
public string OrgName {get;set;}
}
[Cmdlet(VerbsCommon.Get, "Organization")]
[OutputType(typeof(OrgModel))]
public GetOrganizationCmdlet : PSCmdlet
{
[Alias("OrgName")]
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0, HelpMessage = "The orgname.")]
string Name{get;set}
...
}
I have another cmdlet called Department which returns a model DepartmentModel. Get-Department -OrgName <somename> returns all departments inside the Orgname. The cmdlet is defined as below.
[Cmdlet(VerbsCommon.Get, "Department")]
public GetDepartmentCmdlet : PSCmdlet
{
[Parameter(Mandatory = true, ValueFromPipelineByPropertyName = true, Position = 0, HelpMessage = "The org name.")]
[ValidateNotNullOrEmpty]
string OrgName {get;set}
[Parameter(Mandatory = false, ValueFromPipelineByPropertyName = true, Position = 1, HelpMessage = "Optional. The department name.")]
string Name{get;set}
...
}
After loading the module, everything works as expected. The place where it breaks is piping. The below returns an error
Get-Organization -Name <somename> | Get-Department
As you can see, the return type OrgModel has a property defined called OrgName which should automatically bind to Get-Department parameter OrgName but it is not and giving the below error:
C:\WINDOWS\system32> Get-Organization -Name contoso | Get-Department
Get-Department : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.
At line:1 char:44
+ ... et-Organization -Name contoso | Get-Department
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (OrgModel:PSObject) [Get-Department], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,GetDepartment
Any idea ?

Using the command that #PeterSerAl mentioned I noticed the return time was incorrectly set to a collection.
It works after fixing it.
Thanks

Related

Powershell script with standard cmdlets crashes with System.IO.FileLoadException on some machines

We have a script that issues web requests using Invoke-WebRequest and Invoke-RestMethod. It also uses ConvertFrom-Json, ConvertTo-Json, and [System.Net.ServicePointManager]::CertificatePolicy to handle secure connections.
Apart from that, there is one function and one class.
The script works fine on several machines, but crashes with the following message right at the start:
The given assembly name or codebase was invalid. (Exception from HRESULT: 0x80131047)
At line:1 char:1
+ .\newNounFamily.ps1
+ ~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], FileLoadException
+ FullyQualifiedErrorId : System.IO.FileLoadException
We tried extracting more info using this command:
$Error[0].Exception.FileName
But the output was empty.
The stack trace is below. It is not saying much:
at System.Reflection.AssemblyName.nInit(RuntimeAssembly& assembly, Boolean forIntrospection, Boolean raiseResolveEvent)
at System.Reflection.AssemblyName..ctor(String assemblyName)
at System.Management.Automation.Language.TypeDefiner.DefineTypes(Parser parser, Ast rootAst, TypeDefinitionAst[] typeDefinitions)
at System.Management.Automation.Language.Compiler.DefinePowerShellTypes(Ast rootForDefiningTypes, TypeDefinitionAst[] typeAsts)
at System.Management.Automation.Language.Compiler.GenerateTypesAndUsings(ScriptBlockAst rootForDefiningTypesAndUsings, List`1 exprs)
at System.Management.Automation.Language.Compiler.CompileSingleLambda(ReadOnlyCollection`1 statements, ReadOnlyCollection`1 traps, String funcName, IScriptExtent entryExtent, IScriptExtent exitExtent, ScriptBlockAst rootForDefiningTypesAndUsings)
at System.Management.Automation.Language.Compiler.CompileNamedBlock(NamedBlockAst namedBlockAst, String funcName, ScriptBlockAst rootForDefiningTypes)
at System.Management.Automation.Language.Compiler.VisitScriptBlock(ScriptBlockAst scriptBlockAst)
at System.Management.Automation.Language.Compiler.Compile(CompiledScriptBlockData scriptBlock, Boolean optimize)
at System.Management.Automation.CompiledScriptBlockData.ReallyCompile(Boolean optimize)
at System.Management.Automation.CompiledScriptBlockData.CompileOptimized()
at System.Management.Automation.CompiledScriptBlockData.Compile(Boolean optimized)
at System.Management.Automation.PSScriptCmdlet..ctor(ScriptBlock scriptBlock, Boolean useNewScope, Boolean fromScriptFile, ExecutionContext context)
at System.Management.Automation.CommandProcessor.Init(IScriptCommandInfo scriptCommandInfo)
at System.Management.Automation.CommandDiscovery.GetScriptAsCmdletProcessor(IScriptCommandInfo scriptCommandInfo, ExecutionContext context, Boolean useNewScope, Boolean fromScriptFile, SessionStateInternal sessionState)
at System.Management.Automation.CommandDiscovery.CreateCommandProcessorForScript(ExternalScriptInfo scriptInfo, ExecutionContext context, Boolean useNewScope, SessionStateInternal sessionState)
at System.Management.Automation.CommandDiscovery.CreateScriptProcessorForSingleShell(ExternalScriptInfo scriptInfo, ExecutionContext context, Boolean useLocalScope, SessionStateInternal sessionState)
at System.Management.Automation.CommandDiscovery.LookupCommandProcessor(CommandInfo commandInfo, CommandOrigin commandOrigin, Nullable`1 useLocalScope, SessionStateInternal sessionState)
at System.Management.Automation.CommandDiscovery.LookupCommandProcessor(String commandName, CommandOrigin commandOrigin, Nullable`1 useLocalScope)
at System.Management.Automation.ExecutionContext.CreateCommand(String command, Boolean dotSource)
at System.Management.Automation.PipelineOps.AddCommand(PipelineProcessor pipe, CommandParameterInternal[] commandElements, CommandBaseAst commandBaseAst, CommandRedirection[] redirections, ExecutionContext context)
at System.Management.Automation.PipelineOps.InvokePipeline(Object input, Boolean ignoreInput, CommandParameterInternal[][] pipeElements, CommandBaseAst[] pipeElementAsts, CommandRedirection[][] commandRedirections, FunctionContext funcContext)
at System.Management.Automation.Interpreter.ActionCallInstruction`6.Run(InterpretedFrame frame)
at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)
It appears that the Powershell crashes when defining its own types, but the Powershell version and the .NET version seems up to date and perfectly aligned with those on the working machines:
$PSVersionTable.PSVersion
Major Minor Build Revision
----- ----- ----- --------
5 1 18362 145
and here:
[environment]::Version
Major Minor Build Revision
----- ----- ----- --------
4 0 30319 42000
The same machine can run another smaller script, using Invoke-WebRequest and ConvertFrom-Json.
What are we doing wrong, and how do we know what Powershell has issues with?
EDIT: the code parts.
The top:
## =============================================================================
##
## This script's purpose is to add a new noun family and new lexemes in English and another language, link them, and tag them
##
## =============================================================================
Param(
[Parameter(Mandatory = $true, valueFromPipeline=$true, HelpMessage="LaMP user: ")][String] $user,
[Parameter(Mandatory = $true, HelpMessage="LaMP password: ")][String] $password,
[Parameter(HelpMessage="Language code: ")][Int32] $lang,
[Parameter(HelpMessage="Definition: ")][String] $definition,
[Parameter(HelpMessage="Wikidata ID: ")][String] $wikidata,
[Parameter(Mandatory = $true, HelpMessage="English lemmas, delimited by commas")][String[]] $english,
[Parameter(HelpMessage="Native lemmas, delimited by commas ")][String[]] $native,
[Parameter(Mandatory = $true, HelpMessage="Family ID ")][Int32] $family,
[Parameter(Mandatory = $true, HelpMessage="Hypernym ID ")][Int32] $hypernym,
[Parameter()][Int32] $proper,
[Parameter()][Int32] $person
)
The next line:
Function AddLexeme($familyId, $word) {
The first piece after the function:
If (-not ("TrustAllCertsPolicy" -as [type])) {
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;
}
}
"#
}

Powershell function Params getting unexpected values when CSV fields are not present

I am writing a PowerShell function which can take pipeline input. Specifically I am testing it with Import-CSV. Many of the params are not mandatory, which means sometimes the CSV will not have those columns. For boolean values this is working as expected, but with string values, a missing CSV field yields a copy of the row object in the string field.
Here is an example problem parameter:
[Parameter(Mandatory=$False,
ValueFromPipeline=$True,
ValueFromPipelinebyPropertyName=$True,
HelpMessage="TCP Port of the remote server")]
[Alias('Port', 'Remote Server Port')]
[string]$RemoteServerPort = "5500",
Now, if the field is missing, I would expect the value to be "5500" as specified, but instead I get:
$RemoteServerPort = #{Name=KG; IP=10.1.1.1; Username=Admin; Password=}
I've done some looking around, but frankly I'm not even sure what to search for.
This is because you specified ValueFromPipeline=$True so that PoSh coerces the piped object to a string if it cannot bind the parameter by property name. You could solve that by removing ValueFromPipeline=$True from this parameter and introduce another one to be bound to the piped object, i.e. something like this
function MyTestFunc() {
param(
[Parameter(ValueFromPipeline=$True)]
[object]$PipedObj,
[Parameter(Mandatory=$False,
ValueFromPipelinebyPropertyName=$True,
HelpMessage="TCP Port of the remote server")]
[Alias('Port', 'Remote Server Port')]
[string]$RemoteServerPort = "5500"
)
Write-Host "Port: $RemoteServerPort / Obj: $PipedObj"
}
$o1 = [pscustomobject]#{"Server" = "123"; "Port" = "12345"}
$o2 = [pscustomobject]#{"Server" = "1234"; "OS" = "Win3.1"}
$o1 | MyTestFunc
$o2 | MyTestFunc
Will result in
Port: 12345 / Obj: #{Server=123; Port=12345}
Port: 5500 / Obj: #{Server=1234; OS=Win3.1}
A way to see in detail what is actually happening behind the scenes is to use Trace-Command like so
Trace-Command ParameterBinding {$o2 | MyTestFunc} -PSHost

PowerShell InvokeGet the directory property cannot be found

We needed to retrieve the information in active directory concerning 'Terminal Services'. For this I've created a function that works fine most of the time. However, with some users we have issues.
The code:
Function Get-ADTSProfile {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,Position=0)]
[String] $DistinguishedName,
[parameter(Mandatory=$true,Position=1)]
[ValidateNotNullOrEmpty()]
[ValidateSet('UserProfile','AllowLogon','HomeDirectory','HomeDrive')]
[String]$Property
)
Begin {
$User = [ADSI]"LDAP://$DistinguishedName"
}
Process {
Switch ($Property) {
'AllowLogon' {if ($($User.psbase.InvokeGet('allowLogon')) -eq '1'){$True}else{$False}}
'HomeDirectory' {$User.psbase.InvokeGet('TerminalServicesHomeDirectory')}
'HomeDrive' {$User.psbase.InvokeGet('TerminalServicesHomeDrive')}
'UserProfile' {$User.psbase.InvokeGet('TerminalServicesProfilePath')}
}
}
}
The error:
Get-ADTSProfile -DistinguishedName 'CN=test\, test (Den Bosch) NLD,OU=Users,OU=Disabled,OU=NLD,OU=EU,DC=domain,DC=net' -Property 'UserProfile'
Exception calling "InvokeGet" with "1" argument(s): "The directory property cannot be fo
und in the cache.
"
At S:\Test\Brecht\Testie.ps1:84 char:38
+ 'UserProfile' {$User.psbase.InvokeGet('TerminalServicesPro ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodTargetInvocation
I can't really figure out why it works on some and not on all..
I've been working on a recent project that uses ADSI to set and read Terminal Services attributes. From my testing anytime you perform a "InvokeGet({TS Attribute})" a COM exception will be thrown with the message "The directory property cannot be found in cache"
This seems to occur only when the "userParameters" attribute is not set in AD. Maybe the attribute internally checks the ADSI cache for userParameters? So i'm thinking logically you could check the DirectoryEntry for userParameters first, then try and read the properties, or else set it to construct the blob
if ($user.Properties.Contains("userParameters"))
{
#Read the Property from ADSI
Write-Host $user.InvokeGet("TerminalServicesProfilePath")
} else {
#Set the property to construct the userParameter blob
$user.InvokeSet("TerminalServicesProfilePath", "\\somepath")
$user.CommitChanges()
}
Even if the userParameters attribute is not set, you can still perform an InvokeSet to have it constructed

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