Why doesn't $hash.key syntax work inside the ExpandString method? - powershell

The following Powershell script demonstrates the issue:
$hash = #{'a' = 1; 'b' = 2}
Write-Host $hash['a'] # => 1
Write-Host $hash.a # => 1
# Two ways of printing using quoted strings.
Write-Host "$($hash['a'])" # => 1
Write-Host "$($hash.a)" # => 1
# And the same two ways Expanding a single-quoted string.
$ExecutionContext.InvokeCommand.ExpandString('$($hash[''a''])') # => 1
$ExecutionContext.InvokeCommand.ExpandString('$($hash.a)') # => Oh no!
Exception calling "ExpandString" with "1" argument(s): "Object reference not set to an instance of an object."
At line:1 char:1
+ $ExecutionContext.InvokeCommand.ExpandString('$($hash.a)')
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : NullReferenceException
Anyone know why the $hash.key syntax works everywhere but inside explicit expansion? Can this be fixed, or do I have to suck it up and live with the $hash[''key''] syntax?

I use this method, since this bug exists in v4 (not in v5)
function render() {
[CmdletBinding()]
param ( [parameter(ValueFromPipeline = $true)] [string] $str)
#buggy
#$ExecutionContext.InvokeCommand.ExpandString($str)
"#`"`n$str`n`"#" | iex
}
Usage for your example:
'$($hash.a)' | render

The ExpandString api is not exactly meant for use from PowerShell scripts, it was added more for C# code. It's still a bug that your example doesn't work (and I think it's been fixed in V4), but it does mean there is a workaround - one that I recommend for general use.
Double quoted strings effectively (but not literally) call ExpandString. So the following should be equivalent:
$ExecutionContext.InvokeCommand.ExpandString('$($hash.a)')
"$($hash.a)"

I was trying to store text that prompts the user in a text file. I wanted to be able to have variables in the text file that are expanded from my script.
My settings are stored in a PSCustomObject called $profile and so in my text I was trying to do something like:
Hello $($profile.First) $($profile.Last)!!!
and then from my script I was trying to do:
$profile=GetProfile #Function returns PSCustomObject
$temp=Get-Content -Path "myFile.txt"
$myText=Join-String $temp
$myText=$ExecutionContext.InvokeCommand.ExpandString($myText)
which of course left me with the error
Exception calling "ExpandString" with "1" argument(s): "Object
reference not set to an instance of an object."
Finally I figured out I only needed to to store the PSCustomObject values I want in regular old variables, change the text file to use those instead of the object.property version and everything worked nicely:
$profile=GetProfile #Function returns PSCustomObject
$First=$profile.First
$Last=$profile.Last
$temp=Get-Content -Path "myFile.txt"
$myText=Join-String $temp
$myText=$ExecutionContext.InvokeCommand.ExpandString($myText)
And in the text I changed to
Hello $First $Last!!!

Related

Powershell Invoke error parameter number mismatch

Hello i want load a assembly from byte array and i found this:
Loading a .NET assembly from a byte array
It only loads .NET byte array.
And here is my code i changed it a little bit:
$ByteArray = (Invoke-WebRequest "https://DOTNETEXEfile.exe").Content
# Base64
$Base64String = [System.Convert]::ToBase64String($ByteArray);
$PsEBytes = [System.Convert]::FromBase64String($Base64String)
# Run EXE in memory
$assembly = [System.Reflection.Assembly]::Load($PsEBytes)
# Get the static method that is the executable's entry point.
# Note:
# * Assumes 'Program' as the class name,
# and a static method named 'Main' as the entry point.
# * Should there be several classes by that name, the *first*
# - public or non-public - type returned is used.
# If you know the desired type's namespace, use, e.g.
# $assembly.GetType('MyNameSpace.Program').GetMethod(...)
$entryPointMethod =
$assembly.GetTypes().Where({ $_.Name -eq 'Program' }, 'First').
GetMethod('Main', [Reflection.BindingFlags] 'Static, Public, NonPublic')
# Now you can call the entry point.
# This example passes two arguments, 'foo' and 'bar'
$entryPointMethod.Invoke($null, (, [string[]] ('foo', 'bar')))
But we have a problem, It works when i put my link of a c# project that has a msgbox and its .NET, its works fine but when i put a form app exe (.NET) it gives me that error:
PS C:\Users\sadettin\Desktop> C:\Users\sadettin\Desktop\PE.ps1
Exception calling "Invoke" with "2" argument(s): "parameter number mismatch."
At C:\Users\sadettin\Desktop\PE.ps1:25 char:1
+ $entryPointMethod.Invoke($null, (, [string[]] ('foo', 'bar')))
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : TargetParameterCountException
but when i replace $entryPointMethod.Invoke($null, (, [string[]] ('foo', 'bar'))) with $entryPointMethod.Invoke($null, $null) it works
Also when i put a exe (its not .NET) it doesn't work so is there have a way to execute all exe types?

Weird PowerShell problem: [ref] cannot be applied to a variable that does not exist

My Powershell script exited with "[ref] cannot be applied to a variable that does not exist" after running a while (it actually worked for a while)
The code snippet is something like
function outputData(...) {
$data = $null
if ($outputQueue.TryTake([ref] $data, 1000) -eq $false) {
continue
}
Write-Host $data
}
The detail errors thrown at the end are as below:
[ref] cannot be applied to a variable that does not exist.
At C:\Program Files\mfile.ps1:1213 char:13
+ if ($outputQueue.TryTake([ref] $data, 1000) -eq $ ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (data:VariablePath) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : NonExistingVariableReference
May i ask if any thoughts about the cause ?
Thanks !
While error messages aren't always helpful, this one is:
It tells you that the $data variable you're trying to use with [ref] must already exist, i.e., must have been created explicitly, which in PowerShell means:
creating it by assigning a value to it - even if that value is $null,
or using New-Variable to create it.
A simplified example:
$data = $null # create variable $data
# OK to use $data with [ref], now that it exists.
# $data receives [int] value 10 in the process.
[int]::TryParse('10', [ref] $data)

ValidateScript unexpectedly returning false when condition is true

I have a PowerShell function I'm writing to build and execute a variety of logman.exe commands for me so I don't have to reference the provider GUIDs and type up the command each time I want to capture from a different source. One of the parameters is the file name and I am performing some validation on the parameter. Originally I used -match '.+?\.etl$' to check that the file name had the .etl extension and additionally did some validation on the path. I later decided to remove the path validation but neglected to change the validation attribute to ValidatePattern.
What I discovered was that while it worked perfectly on the machine I was using to author and validate it, on my Server 2016 Core machine it seemed to misbehave when calling the function but that if I just ran the same check at the prompt it worked as expected.
The PowerShell:
[Parameter(ParameterSetName="Server", Mandatory=$true)]
[Parameter(ParameterSetName="Client", Mandatory=$true)]
[ValidateScript({$FileName -match '.+?\.etl$'}]
[string] $FileName = $null
The Output:
PS C:\Users\Administrator> Start-TBLogging -ServerLogName HTTPSYS -FileName ".\TestLog.etl"
PS C:\Users\Administrator> Start-TBLogging : Cannot validate argument on parameter 'FileName'. The "$FileName -match '.+?\.etl$'" validation script
for the argument with value ".\TestLog.etl" did not return a result of True. Determine why the validation script failed,
and then try the command again.
At line:1 char:50
+ Start-TBLogging -ServerLogName HTTPSYS -FileName ".\TestLog.etl"
+ ~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Start-TBLogging], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Start-TBLogging
Trying it manually worked:
PS C:\Users\Administrator> $FileName = ".\TestLog.etl"
PS C:\Users\Administrator> $FileName -match '.+?\.etl$'
True
After changing the function to use ValidatePattern it works just fine everywhere but I was wondering if anyone could shed light on the discontinuity.
As Joshua Shearer points out in a comment on a question, you must use automatic variable $_ (or its alias form, $PSItem), not the parameter variable to refer to the argument to validate inside [ValidateScript({ ... })].
Therefore, instead of:
# !! WRONG: The argument at hand has NOT yet been assigned to parameter
# variable $FileName; by design, that assignment
# doesn't happen until AFTER (successful) validation.
[ValidateScript({ $FileName -match '.+?\.etl$' }]
[string] $FileName
use:
# OK: $_ (or $PSItem) represents the argument to validate inside { ... }
[ValidateScript({ $_ -match '.+?\.etl$' })]
[string] $FileName
As briantist points out in another comment on the question, inside the script block $FileName will have the value, if any, from the caller's scope (or its ancestral scopes).

You cannot call a method on a null-valued expression?

I'm trying to take the input "name" and find the whitespace so that I can find the first and last letter of the first and last name entered. But it's telling me "You cannot call a method on a null-valued expression." Help?
$name = Read-Host "Please enter the zip of person $count"
$length = $name.Length
$name = $name.split(" ")
write-host $name[0]
You made a significant change to your post. It used to be:
$name = Read-Host "Please enter the name of person $count"
$length = $name.Length
$pos = $name.IndexOf('\s')
print $pos
Lets ignore the $length line since you do not use it. It looks like you are trying to split on the first white space using regular expressions. You are using .IndexOf() which if you examine the overloads will see it expects strings or a char. Not earth shattering but the point is it is looking for string literals and does not support regular expressions. Consider the following statements
PS C:\Users\Cameron> "John Smith".IndexOf('\s')
-1
PS C:\Users\Cameron> "John\sSmith".IndexOf('\s')
4
The first returned -1 since the string \s was not found. Then we put that string between John and Smith and we now get a positive return since the string literal was matched.
What you were possibly trying to do was use the -split which I base on your original question and the edit. -split supports regular expressions.
PS C:\Users\Cameron> $name = "John Smith"
PS C:\Users\Cameron> ($name -split '\s+')[0]
John
What the second line of code did was split the string $name on the group of white-space. We returned the first element which would be 'John'. The second, not shown since it should be obvious is 'Smith'. Neither elements have trailing or leading white-space ( based only on this example. Mileage will vary on other strings.)
Also, the command print is and alias for Out-Printer. Doubt you meant that command and were most likely looking for, like Dane Boulton suggested, Write-Host or Write-Output. Note: Look them up to understand an important difference. Former writes to console where the ladder writes to the output stream.
Lastly good guess would be that the error was caused by $name being null at the time $name.IndexOf('\s') was called. Easy to simulate as well.
PS C:\Users\Cameron> $something = $null
PS C:\Users\Cameron> $something.Method()
You cannot call a method on a null-valued expression.
At line:1 char:1
+ $something.Method()
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
im not sure which is causing you to get the null value error. But to get the first and last name just use this:
$name = "John Smith"
$name = $name.split(" ")
$name[0] #outputs John
$name[1] #outputs Smith
Also you want write-host or write-output not print

How to provide Linux-style parameter names in a powershell script

I would like to create a Powershell script that takes parameters in the standard Linux style, i.e., --my-param, with a leading --. I thought this might be possible using the alias parameter attribute, as in
Param (
[parameter(Mandatory=$true)]
[alias("-my-param","p")]
[String]
$param
)
What I hoped with this was that the call
c:\src\ps\params.ps1 --my-param "x"
would be recognized as referring to the alias -my-param. Unfortunately, what I get is
C:\src\ps\params.ps1 : A positional parameter cannot be found that accepts argument 'x'.
At line:1 char:21
+ c:\src\ps\params.ps1 <<<< --my-param1 "x"
+ CategoryInfo : InvalidArgument: (:) [params.ps1], ParameterBindingException
+ FullyQualifiedErrorId : PositionalParameterNotFound,params.ps1
On the other hand, using the alias my-param in this lets me call the script with -my-param.
Is there a way of specifying parameter names with leading -- in Powershell?
Your syntax fails at tokenizer level. Compare:
[Management.Automation.PSParser]::Tokenize(
'Command -parameter',
[ref]$null
)
...and...
[Management.Automation.PSParser]::Tokenize(
'Command --parameter',
[ref]$null
)
As you can see former is seen by parser as parameter, latter - as argument.
So the only way would be parsing all arguments "internally" and guessing what is parameter (from your perspective), and what is argument.
I'm not aware of any libraries that will parse Unix-style parameters for you (which doesn't necessarily mean there isn't one...), but you could just not declare any parameters, and parse the parameters yourself in the body of the script.
This will create a hashtable of the parameters, where they keys are the parameter names and the values are the parameter values. Switch parameters will have null values.
$params = #{}
$MyInvocation.Line.Substring(($MyInvocation.Line.IndexOf('--') + 2)) -split ' --' | %{
$_ -match '(\S+) ?(.+)?' | Out-Null
$params.($matches[1]) = $matches[2]
}
$MyInvocation.Line gives you the command line that was used to invoke the script. $MyInvocation.Line.Substring(($MyInvocation.Line.IndexOf('--') + 2)) gives you everything following the first --.
$_ -match '(\S+) ?(.+)?' assigns the parameter name to the first match group, and the value to the second match group. The Out-Null prevents PowerShell from printing True for each iteration.
The reason I used (.+)? rather than (.*) is to make the values of switch parameters null. (.*) will match an empty string if there is nothing to match, making the value of $matches[2] an empty string, whereas (.+)? won't match, making $matches[2] null.
This is assuming that all parameters begin with --. If you want to allow a single hyphen, restrict single-dash parameter names to a single letter, or check for incorrectly declared parameters (for example throw an error if there's a triple-hyphen), you'll have to account for that in your code, but this is the basic idea.