powershell function parameter types - powershell

have the following function:
function appendToSB([System.Text.StringBuilder]$sb,
[string]$value){
[void]$sb.append($value)
$sb
}
$sb = new-object -typename system.text.stringbuilder
$sb = appendToSb($sb, "1,")
$sb.tostring() | out-host
i want to build string using StringBuilder using my function for that, but i receive the following error:
appendToSB : Cannot process argument transformation on parameter 'sb'.
Cannot convert the "System.Object[]" value of ty pe "System.Object[]"
to type "System.Text.StringBuilder". At E:\powershell\test.ps1:8
char:11
+ appendToSb([system.text.stringbuilder]$sb, "1,")
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [appendToSB], ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,appendToSB
does anybody can explain how function/function parameter/return values works in powershell?

Classic PowerShell issue. You don't use parens or comma separated args when calling commands or functions e.g.:
appendToSb $sb "1,"
You only use that syntax when calling .NET methods. If you use Set-StrictMode -Version 2 it will catch this sort of issue. What you passed ($sb, "1,") is how you would pass an array to a single parameter. Technically the parens aren't needed but don't change the value i.e. you could pass an array like this as well $sb, ",".

Related

Create new System.Collections.Generic.Dictionary object fails in PowerShell

PowerShell version: 5.x, 6
I'm trying to create a new object of System.Collections.Generic.Dictionary, but it fails.
I tried the following "versions":
> $dictionary = new-object System.Collections.Generic.Dictionary[[string],[int]]
New-Object : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'ComObject'. Specified method is not supported.
At line:1 char:25
+ ... ry = new-object System.Collections.Generic.Dictionary[[string],[int]]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-Object], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.NewObjectCommand
> $dictionary = new-object System.Collections.Generic.Dictionary[string,int]
New-Object : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'ComObject'. Specified method is not supported.
At line:1 char:25
+ ... ionary = new-object System.Collections.Generic.Dictionary[string,int]
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [New-Object], ParameterBindingException
+ FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.NewObjectCommand
I know that I can use a hashtable under PowerShell, but I want to know how to create a dictionary via the above declaration.
What am I missing?
Thx
Used type name System.Collections.Generic.Dictionary[[string],[int]] contains a comma. By Creating and initializing an array:
To create and initialize an array, assign multiple values to a
variable. The values stored in the array are delimited with a
comma…
Hence, you need to escape the comma (read the about_Escape_Characters and about_Quoting_Rules help topics). There are more options:
In Windows PowerShell, the escape character is the backtick (`),
also called the grave accent (ASCII 96).
$dictionary = new-object System.Collections.Generic.Dictionary[[string]`,[int]]
Quotation marks are used to specify a literal string. You can enclose
a string in single quotation marks (') or double quotation marks
(").
$dictionary = new-object "System.Collections.Generic.Dictionary[[string],[int]]"
or
$dictionary = new-object 'System.Collections.Generic.Dictionary[[string],[int]]'
The problem is how powershell is interpreting your argument.
When you include a comma in your string, it's now trying to bind
'System.Collections.Generic.Dictionary[[string]', '[int]]'
to the -TypeName parameter which is of type <string[]> or in the error message, <System.Object[]>. This can be solved by properly quoting your argument so it matches the expected parameter binding of <string>:
New-Object -TypeName 'System.Collections.Generic.Dictionary[[string], [int]]'
As well as the accepted answer, a dictionary can also be initialised using the syntax in the code block below, which:
removes the need to escape any characters
is much cleaner (in my opinion)
gives you intellisense (tested in PowerShell & PowerShell ISE)
$dictionary = [System.Collections.Generic.Dictionary[string,int]]::new()
... where string and int are .NET types.

PowerShell error 'can't call null-value expresssion' [duplicate]

I am simply trying to create a powershell script which calculates the md5 sum of an executable (a file).
My .ps1 script:
$answer = Read-Host "File name and extension (ie; file.exe)"
$someFilePath = "C:\Users\xxx\Downloads\$answer"
If (Test-Path $someFilePath){
$stream = [System.IO.File]::Open("$someFilePath",[System.IO.Filemode]::Open, [System.IO.FileAccess]::Read)
$hash = [System.BitConverter]::ToString($md5.ComputeHash($stream))
$hash
$stream.Close()
}
Else{
Write-Host "Sorry, file $answer doesn't seem to exist."
}
Upon running my script I receive the following error:
You cannot call a method on a null-valued expression.
At C:\Users\xxx\Downloads\md5sum.ps1:6 char:29
+ $hash = [System.BitConverter]::ToString($md5.Compute ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
To my understanding, this error means the script is attempting to do something, but another part of the script does not have any information to permit the first part of the script to work properly. In this case, $hash.
Get-ExecutionPolicy outputs Unrestricted.
What is causing this error?
What exactly is my null valued expression?
Any help is appreciated. I apologize if this is trivial and will continue my research.
References:
http://blogs.technet.com/b/heyscriptingguy/archive/2013/03/27/troubleshoot-the-invokemethodonnull-error-with-powershell.aspx
How to get an MD5 checksum in PowerShell
The simple answer for this one is that you have an undeclared (null) variable. In this case it is $md5. From the comment you put this needed to be declared elsewhere in your code
$md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
The error was because you are trying to execute a method that does not exist.
PS C:\Users\Matt> $md5 | gm
TypeName: System.Security.Cryptography.MD5CryptoServiceProvider
Name MemberType Definition
---- ---------- ----------
Clear Method void Clear()
ComputeHash Method byte[] ComputeHash(System.IO.Stream inputStream), byte[] ComputeHash(byte[] buffer), byte[] ComputeHash(byte[] buffer, int offset, ...
The .ComputeHash() of $md5.ComputeHash() was the null valued expression. Typing in gibberish would create the same effect.
PS C:\Users\Matt> $bagel.MakeMeABagel()
You cannot call a method on a null-valued expression.
At line:1 char:1
+ $bagel.MakeMeABagel()
+ ~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
PowerShell by default allows this to happen as defined its StrictMode
When Set-StrictMode is off, uninitialized variables (Version 1) are assumed to have a value of 0 (zero) or $Null, depending on type. References to non-existent properties return $Null, and the results of function syntax that is not valid vary with the error. Unnamed variables are not permitted.

How to pass a custom enum to a function in powershell

When defining a function, how can you reference a custom enum?
Here's what I'm trying:
Add-Type -TypeDefinition #"
namespace JB
{
public enum InternetZones
{
Computer
,LocalIntranet
,TrustedSites
,Internet
,RestrictedSites
}
}
"# -Language CSharpVersion3
function Get-InternetZoneLogonMode
{
[CmdletBinding()]
param
(
[Parameter(Mandatory=$true)]
[JB.InterfaceZones]$zone
)
[string]$regpath = ("HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\{0}" -f [int]$zone)
$regpath
#...
#Get-PropertyValue
}
Get-InternetZoneLogonMode -zone [JB.InternetZones]::TrustedSites
But this gives the error:
Get-ZoneLogonMode : Unable to find type [JB.InterfaceZones]. Make sure that the assembly that contains this type is loaded.
At line:29 char:1
+ Get-ZoneLogonMode -zone [JB.InternetZones]::TrustedSites
+ ~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (JB.InterfaceZones:TypeName) [], RuntimeException
+ FullyQualifiedErrorId : TypeNotFound
NB: I'm aware I could use ValidateSet for similar functionality; however that has the disadvantage of only having a name value; as opposed to allowing me to program using friendly names which are then mapped to ints in the background (I could write code for that; but enum seems more appropriate if possible).
I'm using Powershell v4, but ideally I'd like a PowerShell v2 compatible solution as most users are on that version by default.
Update
I've corrected the typo (thanks PetSerAl; well spotted).
[JB.InterfaceZones]$zone now changed to [JB.InternetZones]$zone.
Now I'm seeing error:
Get-InternetZoneLogonMode : Cannot process argument transformation on parameter 'zone'. Cannot convert value "[JB.InternetZones]::TrustedSites" to type
"JB.InternetZones". Error: "Unable to match the identifier name [JB.InternetZones]::TrustedSites to a valid enumerator name. Specify one of the following
enumerator names and try again: Computer, LocalIntranet, TrustedSites, Internet, RestrictedSites"
At line:80 char:33
+ Get-InternetZoneLogonMode -zone [JB.InternetZones]::TrustedSites
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidData: (:) [Get-InternetZoneLogonMode], ParameterBindingArgumentTransformationException
+ FullyQualifiedErrorId : ParameterArgumentTransformationError,Get-InternetZoneLogonMode
The ISE gave this one away to me but your attempted syntax was not completely incorrect. I was able to do this and get it to work.
Get-InternetZoneLogonMode -Zone ([JB.InternetZones]::TrustedSites)
Again, If you look at the highlighting you will see how I came to that conclusion.
Per comments by PetSerAl & CB:
Corrected typo in function definition
from [JB.InterfaceZones]$zone
to [JB.InternetZones]$zone
Changed function call
from Get-InternetZoneLogonMode -zone [JB.InternetZones]::TrustedSites
to Get-InternetZoneLogonMode -zone TrustedSites

Passing default values in powershell to another script

Given:
setenv.ps1:
param([Parameter(Mandatory=$false)][ValidateSet(541,642,643,644,645,"tmp")]$version=645)
echo "[setenv] Version = $version"
dbupdate.ps1:
param($version)
. setenv $version
echo "[dbupdate] Version = $version"
Output:
PS C:\> dbupdate.ps1
c:\utils\setenv.ps1 : Cannot validate argument on parameter 'version'. The argument is null, empty, or an element of the argument collection contains a null value. Supply a collection that does not
contain any null values and then try the command again.
At c:\utils\dbupdate.ps1:3 char:10
+ . setenv $version
+ ~~~~~~~~
+ CategoryInfo : InvalidData: (:) [setenv.ps1], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,setenv.ps1
[dbupdate] Version =
I want to invoke dbupdate.ps1 without any arguments, which should tell setenv.ps1 use the default value for the $version argument. However, the default value is the implementation detail of the serenv.ps1 script - I do not want it to "leak" into the dbupdate.ps1.
How do I do it?
EDIT
Trying to follow the advice of Cookie Monster yields the following error:
c:\dayforce\utils\setenv.ps1 : Cannot validate argument on parameter 'version'. The argument "System.Collections.Hashtable"
does not belong to the set "541,642,643,644,645,tmp" specified by the ValidateSet attribute. Supply an argument that is in the
set and then try the command again.
At C:\dayforce\utils\dbupdate.ps1:9 char:10
+ . setenv $params
+ ~~~~~~~
+ CategoryInfo : InvalidData: (:) [setenv.ps1], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,setenv.ps1
This is a good use case for splatting your arguments. Particularly if you ever need to do it for more than a single parameter...
Modified dbupdate.ps1:
param($version)
#Build a hashtable containing the parameters and values you want to call
$params = #{}
if($version) { $params.version = $version }
. setenv #params
You could even use #PSBoundParameters, there's an example of this in the help system. Run Get-Help about_Splatting for more information!
Edit for clarification
Not sure why this was downvoted. Here is verification that this syntax works. Calling #params is required, not $params.
Lastly, here is one more example demonstrating splatting PSBoundParameters, even less code:
Get-Help about_Splatting or Googling "PowerShell splatting" will provide more examples if needed!
Your overriding the default value with a value of null. The validateset attribute seems to be causing that error. I tried adding allownull,allowemptystring and allowemptycollection and added a null value to the set but validateset still caused that error. As an alternative you could just do this -
if ($version) {
. .\setenv.ps1 $version
} else {
. .\setenv.ps1
}

Powershell: Args/params not being populated

I have a PowerShell script:
param (
[Parameter(Mandatory=$true)][string]$input,
[Parameter(Mandatory=$true)][string]$table
)
Write-Host "Args:" $Args.Length
Get-Content $input |
% { [Regex]::Replace($_, ",(?!NULL)([^,]*[^\d,]+[^,]*)", ",'`$1'") } |
% { [Regex]::Replace($_, ".+", "INSERT INTO $table VALUES (`$1)") }
The Write-Host part is for debugging.
I run it as .\csvtosql.ps1 mycsv.csv dbo.MyTable (from powershell shell), and get
Args: 0
Get-Content : Cannot bind argument to parameter 'Path' because it is an empty s
tring.
At C:\temp\csvtosql.ps1:7 char:12
+ Get-Content <<<< $input |
+ CategoryInfo : InvalidData: (:) [Get-Content], ParameterBinding
ValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAl
lowed,Microsoft.PowerShell.Commands.GetContentCommand
I get exactly the same error with any parameters that I pass, also the same error if I try to use named parameters.
What can cause parameters not to be passed in?
UPDATE: PowerShell ISE asks me for these parameters using GUI prompts, then gives me the same error about them not being passed in.
Unless you marked a parameter with the ValueFromRemainingArguments attribute (indicates whether the cmdlet parameter accepts all the remaining command-line arguments that are associated with this parameter), Args is "disabled". If all you need is the arguments count call the special variable:
$PSBoundParameters.Count
Do not mix. Make use of $args or parameters.
Also do note that $input is a special variable, don't declare it as a parameter. http://dmitrysotnikov.wordpress.com/2008/11/26/input-gotchas/
You're calling your script with positional parameters (i.e. unnamed) and PowerShell doesn't know how to map them to your script parameters. You need to either call your script using the parameter names:
.\csvtosql.ps1 -input mycsv.csv -table dbo.MyTable
or update your script to specify your preferred order of positional parameters:
param (
[Parameter(Mandatory=$true,Position=0)]
[string]
$input,
[Parameter(Mandatory=$true,Position=1)]
[string]
$table
)