How to allow unspecified named parameters? [duplicate] - powershell

Using [parameter(ValueFromRemainingArguments=$true)] one can get all the remaining arguments passed to the function into a variable as a list.
How can I get the remaining arguments as a hashtable type, for example for inputs like Function -var1 value1 -var2 value2?

There are multiple ways to achieve this. The following solution supports parameters with:
Simple value (single item)
Array value
Null value (switch)
Script:
function testf {
param(
$name = "Frode",
[parameter(ValueFromRemainingArguments=$true)]
$vars
)
"Name: $name"
"Vars count: $($vars.count)"
"Vars:"
#Convert vars to hashtable
$htvars = #{}
$vars | ForEach-Object {
if($_ -match '^-') {
#New parameter
$lastvar = $_ -replace '^-'
$htvars[$lastvar] = $true
} else {
#Value
$htvars[$lastvar] = $_
}
}
#Return hashtable
$htvars
}
testf -simplepar value1 -arraypar value2,value3 -switchpar
Output:
Name: Frode
Vars count: 5
Vars:
Name Value
---- -----
arraypar {value2, value3}
switchpar
simplepar value1
Edit: Modified default value assigned to Hashtable keys: $htvars[$lastvar] = $true. Using $true as the default accounts for switch parameters and can make the resulting Hastable more "splattable".

Updated per Ansgars comment.
One possibility is to build the hash-table within the function. Here is an example:
function MyFunction
{
[CmdletBinding()]
param([parameter(ValueFromRemainingArguments=$true)] $allparams)
process
{
$myhash = #{}
for ($i = 0; $i -lt $allparams.count; $i+=2)
{
$myhash[($allparams[$i]-replace '^-+')] = $allparams[$i+1]
}
}
end
{
$myhash
}
}
We parse through the params in the parameters $allparams using a for loop, and retrieve the key/value pairs to form the hash table, then in the end block we display it.
MyFunction -var1 10 -var2 30 -var3 hello
Name Value
---- -----
var1 10
var3 hello
var2 30

This is tricky because using [Parameter(ValueFromRemainingArguments=$true)] means this is an advanced function, and $args cannot be used in advanced functions.
But if you want a hashtable of all the specified parameters and their values, you could simply use $PSBoundParameters, like so :
function foo {
[cmdletbinding()]
param(
[Parameter(Position=0)]
$Name,
[Parameter(Position=1,ValueFromRemainingArguments=$true)]
$LastName
)
"PSBoundParameters : "
$PSBoundParameters
}
foo -Name Mike Jordan Jones
This results in the following :
PSBoundParameters :
Key Value
--- -----
Name Mike
LastName {Jordan, Jones}
Is this what you are trying to achieve ?

There are a number of caveats with this method but I just wanted to show that ConvertFrom-StringData could also be considered here.
function Convert-StringToHashTable {
param(
[parameter(ValueFromRemainingArguments=$true)]
[string]$string
)
$string -replace "(^| )-","`r`n" | ForEach-Object{$_ -replace "(\w+) (.*)",'$1=$2'} | ConvertFrom-StringData
}
Convert-StringToHashTable -var1-5 value1 -var2 "value2 or 3"
Output from above would be
Name Value
---- -----
var2 value2 or 3
var1-5 value1
We take the all the remaining arguments as a single string. Then we split the string on - that occur at the beginning of the line or after a space. ( This accounts for dashes in the middle of works like Ansgar mentioned in another answer). Then we convert the first space after the first word into an equal sign. That will make a string of key value pairs that ConvertFrom-StringData expects.
Known Caveats
This will not work if you try to send arrays like in Frode's answer. He can handle those.

Related

What is '#{}' meaning in PowerShell

I have line of scripts for review here, I noticed variable declaration with a value:
function readConfig {
Param([string]$fileName)
$config = #{}
Get-Content $fileName | Where-Object {
$_ -like '*=*'
} | ForEach-Object {
$key, $value = $_ -split '\s*=\s*', 2
$config[$key] = $value
}
return $config
}
I wonder what #{} means in $config = #{}?
#{} in PowerShell defines a hashtable, a data structure for mapping unique keys to values (in other languages this data structure is called "dictionary" or "associative array").
#{} on its own defines an empty hashtable, that can then be filled with values, e.g. like this:
$h = #{}
$h['a'] = 'foo'
$h['b'] = 'bar'
Hashtables can also be defined with their content already present:
$h = #{
'a' = 'foo'
'b' = 'bar'
}
Note, however, that when you see similar notation in PowerShell output, e.g. like this:
abc: 23
def: #{"a"="foo";"b"="bar"}
that is usually not a hashtable, but the string representation of a custom object.
The meaning of the #{}
can be seen in diffrent ways.
If the #{} is empty, an empty hash table is defined.
But if there is something between the curly brackets it can be used in a contex of an splatting operation.
Hash Table
Splatting
I think there is no need in explaining what an hash table is.
Splatting is a method of passing a collection of parameter values to a command as unit.
$prints = #{
Name = "John Doe"
Age = 18
Haircolor = "Red"
}
Write-Host #prints
Hope it helps! BR
Edit:
Regarding the updated code from the questioner the answer is
It defines an empty hash table.
Be aware that Get-Content has its own parameters!
THE MOST IMPORTANT 1:
[-Raw]

Powershell script to table doesn't output anything [duplicate]

I have a problem in a PowerShell script:
When I want to pass a Hashtable to a function, this hashtable is not recognized as a hashtable.
function getLength(){
param(
[hashtable]$input
)
$input.Length | Write-Output
}
$table = #{};
$obj = New-Object PSObject;$obj | Add-Member NoteProperty Size 2895 | Add-Member NoteProperty Count 5124587
$table["Test"] = $obj
$table.GetType() | Write-Output ` Hashtable
$tx_table = getLength $table `Unable to convert System.Collections.ArrayList+ArrayListEnumeratorSimple in System.Collections.Hashtable
Why?
$Input is an automatic variable that enumerates the input given.
Chose any other variable name and it'll work - although not necessarily as you might expect - to get the number of entries in a hashtable you need to inspect the Count property:
function Get-Length {
param(
[hashtable]$Table
)
$Table.Count
}
Write-Output is implied when you just leave the $Table.Count as is.
Also, the () suffix in the function name is unnecessary syntactic sugar with zero meaning when you declare your parameters inline with Param() - drop it
I'm not really sure what to comment here, it seems self-explanatory. If not, leave a comment and I'll clarify.
$ExampleHashTable = #{
"one" = "the loneliest number"
"two" = "just as bad as one"
}
Function PassingAHashtableToAFunctionTest {
param(
[hashtable] $PassedHashTable,
[string] $AHashTableElement
)
Write-Host "One is ... "
Write-Host $PassedHashTable["one"]
Write-Host "Two is ... "
Write-Host $AHashTableElement
}
PassingAHashtableToAFunctionTest -PassedHashTable $ExampleHashTable `
-AHashTableElement $ExampleHashTable["two"]
Output:
One is ...
the loneliest number
Two is ...
just as bad as one

How to loop through arrays in hash table - passing parameters based on values read from a CSV file

Curious about how to loop through a hash table where each value is an array. Example:
$test = #{
a = "a","1";
b = "b","2";
c = "c","3";
}
Then I would like to do something like:
foreach ($T in $test) {
write-output $T
}
Expected result would be something like:
name value
a a
b b
c c
a 1
b 2
c 3
That's not what currently happens and my use case is to basically pass a hash of parameters to a function in a loop. My approach might be all wrong, but figured I would ask and see if anyone's tried to do this?
Edit**
A bit more clarification. What I'm basically trying to do is pass a lot of array values into a function and loop through those in the hash table prior to passing to a nested function. Example:
First something like:
$parameters = import-csv .\NewComputers.csv
Then something like
$parameters | New-LabVM
Lab VM Code below:
function New-LabVM
{
[CmdletBinding()]
Param (
# Param1 help description
[Parameter(Mandatory=$true,
Position=0,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[Alias("p1")]
[string[]]$ServerName,
# Param2 help description
[Parameter(Position = 1)]
[int[]]$RAM = 2GB,
# Param3 help description
[Parameter(Position=2)]
[int[]]$ServerHardDriveSize = 40gb,
# Parameter help description
[Parameter(Position=3)]
[int[]]$VMRootPath = "D:\VirtualMachines",
[Parameter(Position=4)]
[int[]]$NetworkSwitch = "VM Switch 1",
[Parameter(Position=4)]
[int[]]$ISO = "D:\ISO\Win2k12.ISO"
)
process
{
New-Item -Path $VMRootPath\$ServerName -ItemType Directory
$Arguments = #{
Name = $ServerName;
MemoryStartupBytes = $RAM;
NewVHDPath = "$VMRootPath\$ServerName\$ServerName.vhdx";
NewVHDSizeBytes = $ServerHardDriveSize
SwitchName = $NetworkSwitch;}
foreach ($Argument in $Arguments){
# Create Virtual Machines
New-VM #Arguments
# Configure Virtual Machines
Set-VMDvdDrive -VMName $ServerName -Path $ISO
Start-VM $ServerName
}
# Create Virtual Machines
New-VM #Arguments
}
}
What you're looking for is parameter splatting.
The most robust way to do that is via hashtables, so you must convert the custom-object instances output by Import-Csv to hashtables:
Import-Csv .\NewComputers.csv | ForEach-Object {
# Convert the custom object at hand to a hashtable.
$htParams = #{}
$_.psobject.properties | ForEach-Object { $htParams[$_.Name] = $_.Value }
# Pass the hashtable via splatting (#) to the target function.
New-LabVM #htParams
}
Note that since parameter binding via splatting is key-based (the hashtable keys are matched against the parameter names), it is fine to use a regular hashtable with its unpredictable key ordering (no need for an ordered hashtable ([ordered] #{ ... }) in this case).
Try this:
for($i=0;$i -lt $test.Count; $i++)
{$test.keys | %{write-host $test.$_[$i]}}
Weirdly, it outputs everything in the wrong order (because $test.keys outputs it backwards).
EDIT: Here's your solution.
Using the [System.Collections.Specialized.OrderedDictionary] type, you guarantee that the output will come out the same order as you entered it.
$test = [ordered] #{
a = "a","1";
b = "b","2";
c = "c","3";
}
After running the same solution code as before, you get exactly the output you wanted.

Reading strings from text files using switch -regex returns null element

Question:
The intention of my script is to filter out the name and phone number from both text files and add them into a hash table with the name being the key and the phone number being the value.
The problem I am facing is
$name = $_.Current is returning $null, as a result of which my hash is not getting populated.
Can someone tell me what the issue is?
Contents of File1.txt:
Lori
234 east 2nd street
Raleigh nc 12345
9199617621
lori#hotmail.com
=================
Contents of File2.txt:
Robert
2531 10th Avenue
Seattle WA 93413
2068869421
robert#hotmail.com
Sample Code:
$hash = #{}
Switch -regex (Get-content -Path C:\Users\svats\Desktop\Fil*.txt)
{
'^[a-z]+$' { $name = $_.current}
'^\d{10}' {
$phone = $_.current
$hash.Add($name,$phone)
$name=$phone=$null
}
default
{
write-host "Nothing matched"
}
}
$hash
Remove the current property reference from $_:
$hash = #{}
Switch -regex (Get-content -Path C:\Users\svats\Desktop\Fil*.txt)
{
'^[a-z]+$' {
$name = $_
}
'^\d{10}' {
$phone = $_
$hash.Add($name, $phone)
$name = $phone = $null
}
default {
Write-Host "Nothing matched"
}
}
$hash
Mathias R. Jessen's helpful answer explains your problem and offers an effective solution:
it is automatic variable $_ / $PSItem itself that contains the current input object (whatever its type is - what properties $_ / $PSItem has therefore depends on the input object's specific type).
Aside from that, there's potential for making the code both less verbose and more efficient:
# Initialize the output hashtable.
$hash = #{}
# Create the regex that will be used on each input file's content.
# (?...) sets options: i ... case-insensitive; m ... ^ and $ match
# the beginning and end of every *line*.
$re = [regex] '(?im)^([a-z]+|\d{10})$'
# Loop over each input file's content (as a whole, thanks to -Raw).
Get-Content -Raw File*.txt | foreach {
# Look for name and phone number.
$matchColl = $re.Matches($_)
if ($matchColl.Count -eq 2) { # Both found, add hashtable entry.
$hash.Add($matchColl.Value[0], $matchColl.Value[1])
} else {
Write-Host "Nothing matched."
}
}
# Output the resulting hashtable.
$hash
A note on the construction of the .NET [System.Text.RegularExpressions.Regex] object (or [regex] for short), [regex] '(?im)^([a-z]+|\d{10})$':
Embedding matching options IgnoreCase and Multiline as inline options i and m directly in the regex string ((?im) is convenient, in that it allows using simple cast syntax ([regex] ...) to construct the regular-expression .NET object.
However, this syntax may be obscure and, furthermore, not all matching options are available in inline form, so here's the more verbose, but easier-to-read equivalent:
$re = New-Object regex -ArgumentList '^([a-z]+|\d{10})$', 'IgnoreCase, Multiline'
Note that the two options must be specified comma-separated, as a single string, which PowerShell translates into the bit-OR-ed values of the corresponding enumeration values.
other solution, use convertfrom-string
$template=#'
{name*:Lori}
{street:234 east 2nd street}
{city:Raleigh nc 12345}
{phone:9199617621}
{mail:lori#hotmail.com}
{name*:Robert}
{street:2531 10th Avenue}
{city:Seattle WA 93413}
{phone:2068869421}
{mail:robert#hotmail.com}
{name*:Robert}
{street:2531 Avenue}
{city:Seattle WA 93413}
{phone:2068869421}
{mail:robert#hotmail.com}
'#
Get-Content -Path "c:\temp\file*.txt" | ConvertFrom-String -TemplateContent $template | select name, phone

Get ValueFromRemainingArguments as an hashtable

Using [parameter(ValueFromRemainingArguments=$true)] one can get all the remaining arguments passed to the function into a variable as a list.
How can I get the remaining arguments as a hashtable type, for example for inputs like Function -var1 value1 -var2 value2?
There are multiple ways to achieve this. The following solution supports parameters with:
Simple value (single item)
Array value
Null value (switch)
Script:
function testf {
param(
$name = "Frode",
[parameter(ValueFromRemainingArguments=$true)]
$vars
)
"Name: $name"
"Vars count: $($vars.count)"
"Vars:"
#Convert vars to hashtable
$htvars = #{}
$vars | ForEach-Object {
if($_ -match '^-') {
#New parameter
$lastvar = $_ -replace '^-'
$htvars[$lastvar] = $true
} else {
#Value
$htvars[$lastvar] = $_
}
}
#Return hashtable
$htvars
}
testf -simplepar value1 -arraypar value2,value3 -switchpar
Output:
Name: Frode
Vars count: 5
Vars:
Name Value
---- -----
arraypar {value2, value3}
switchpar
simplepar value1
Edit: Modified default value assigned to Hashtable keys: $htvars[$lastvar] = $true. Using $true as the default accounts for switch parameters and can make the resulting Hastable more "splattable".
Updated per Ansgars comment.
One possibility is to build the hash-table within the function. Here is an example:
function MyFunction
{
[CmdletBinding()]
param([parameter(ValueFromRemainingArguments=$true)] $allparams)
process
{
$myhash = #{}
for ($i = 0; $i -lt $allparams.count; $i+=2)
{
$myhash[($allparams[$i]-replace '^-+')] = $allparams[$i+1]
}
}
end
{
$myhash
}
}
We parse through the params in the parameters $allparams using a for loop, and retrieve the key/value pairs to form the hash table, then in the end block we display it.
MyFunction -var1 10 -var2 30 -var3 hello
Name Value
---- -----
var1 10
var3 hello
var2 30
This is tricky because using [Parameter(ValueFromRemainingArguments=$true)] means this is an advanced function, and $args cannot be used in advanced functions.
But if you want a hashtable of all the specified parameters and their values, you could simply use $PSBoundParameters, like so :
function foo {
[cmdletbinding()]
param(
[Parameter(Position=0)]
$Name,
[Parameter(Position=1,ValueFromRemainingArguments=$true)]
$LastName
)
"PSBoundParameters : "
$PSBoundParameters
}
foo -Name Mike Jordan Jones
This results in the following :
PSBoundParameters :
Key Value
--- -----
Name Mike
LastName {Jordan, Jones}
Is this what you are trying to achieve ?
There are a number of caveats with this method but I just wanted to show that ConvertFrom-StringData could also be considered here.
function Convert-StringToHashTable {
param(
[parameter(ValueFromRemainingArguments=$true)]
[string]$string
)
$string -replace "(^| )-","`r`n" | ForEach-Object{$_ -replace "(\w+) (.*)",'$1=$2'} | ConvertFrom-StringData
}
Convert-StringToHashTable -var1-5 value1 -var2 "value2 or 3"
Output from above would be
Name Value
---- -----
var2 value2 or 3
var1-5 value1
We take the all the remaining arguments as a single string. Then we split the string on - that occur at the beginning of the line or after a space. ( This accounts for dashes in the middle of works like Ansgar mentioned in another answer). Then we convert the first space after the first word into an equal sign. That will make a string of key value pairs that ConvertFrom-StringData expects.
Known Caveats
This will not work if you try to send arrays like in Frode's answer. He can handle those.